Imports VB = Microsoft.VisualBasic
Public Class clsWRDBImport
    Implements IDisposable

    'Chattahoochee River Modeling Project

    'Database conversion program---written by L.C. Wilson, 11/10/94
    'Converted to VB.Net using VS2005 and Framework 2.0 for WRDB 5.0 11/20/07

    '============================================================================

    'Revision History:

    '  DATE   VER  INITIALS   REVISIONS
    '-------- ---  --------   ---------

    '12/5/94  1.1    LCW      Allow specification of columns, not just PCODEs
    '                         Initial comments revised to reflect new data format.
    '5/9/95   1.2    LCW      Add additional conversion options:  HOUR-DAY and
    '                         DAY-MONTH
    '11/14/95 1.3    LCW      Add additional conversion option: WRDB-CSV
    '11/28/95 1.4    LCW      Correct several bugs found by RB3
    '06/xx/99 1.5    LCW      Add COLUMNAR format and speed up operations
    '                         Remove space-delimited for Support file imports
    '08/02/00 4.1    LCW      Add DATE-HOUR format
    '01/31/01 4.2    LCW      Add DSS Import feature
    '11/03/03 5.0    LCW      Overhaul program to read/write tables and be DLL
    '08/13/07 6.0    LCW      Convert to VB.Net 2005 and use ADO.Net

    '============================================================================

    'This conversion program will interpret the command line, and open the input
    'file which is expected to conform to one of several formats:

    'TABULAR FORMAT:

    'Line 1:  Keyword, File description
    'Line 2:  STATION_ID,NUMCOLUMNS,ROW_INCR,MIN_INCR
    'Line 3:  COLUMN1,COLUMN2,COLUMN3...
    'Line 4:  CCODE1, CCODE2, CCODE3...
    'Line 5:  Mult1,  Mult2,  Mult3...
    'Line 6:  Value1, Value2, Value3.....
    '...line 6 is repeated until the end of file is reached

    'All fields must be comma, tab, or space delimited.  Cells may not have
    'embedded spaces in fields.  If a file is space delimited, no blank fields
    'are allowed.  All values will be read as strings.

    'Any line beginning with a semi-colon ";" will be ignored.

    'First non-commented line is assumed to be the file description (line 1 above).
    'The file description is intended to confirm you are using the correct file.

    'Line 2 includes a valid station identifier, the number of columns in lines 6+
    '(including blank & IGNORED columns), and two optional fields for filtering
    'the input by row.  A ROW_INCR of n indicates that every nth row will be
    'processed.  Alternatively, MIN_INCR specifies minute desired for input.
    'For example, if 15 minute data are in the input file and only hourly data
    '(on the hour) are needed, MIN_INCR should be set to 60.  If 4-hour values are
    'desired (e.g., 4 a.m., 8 a.m., 12 a.m., etc.), enter 240.

    'Only one of the ROW_INCR or MIN_INCR should be specified, or an error message
    'will be generated

    'The following column names on line 3 apply:

    '  DATE        :  date in the form mm/dd/yy or mm-dd-yy (m/d/yy OK)
    '  JULIAN      :  elapsed days in a given year (user prompted for year)
    '  TIME        :  time in the form hhmm or hh:mm (hmm and hhmmss OK)
    '  TIME_DST    :  time in daylight saving time-will be converted to
    '                 standard time for a given time zone
    '  IGNORE      :  all data in this column ignored
    '  LEW_PCT     :  data for this database field, otherwise is left blank
    '  DEPTH_M     :  data for this database field, otherwise is left blank
    '  PCODE       :  any valid PCODE string in PCODES.DB can be used,
    '                 RESULT values are in columns below PCODEs

    'Column heading JULIAN is used for importing Corps of Engineers monitoring
    'data.  This represents the number of days since January 1 of a specified year.
    'To correctly compute the date, you must now enter the year the data were
    'collected in the form YY. For example, for 1994 data just enter 94.

    'Specify either DATE or JULIAN, not both
    'Use only TIME or TIME_DST, not both, for a given time zone

    'CCodes on Line 4 will be applied to entire columns of PCoded values.
    'Blank CCodes will not be tolerated

    'Line 4 must be included, even if all columns are blank.

    'Multipliers on Line 5 will be applied to values in PCode columns.
    'Special multipliers are:

    '  blank       :  multiplier is not applied or is 1.0
    '  C2F         :  convert degrees C to degrees F''  F2C         :  convert degrees F to degrees C
    '  COEF2C      :  divide degrees F by 10 then convert to degrees C
    '                 (as found on DOE fixed format input)

    'for example, set the multiplier to 1.5472 to convert MGD to CFS.

    'Line 5 must be included, even if all columns are blank (all multipliers are 1.0)

    'RESULT values in PCODE columns include the following special provisions:

    '  <x.xx       :  non-detect value, detection limit specified
    '  <           :  non-detect value, detection limit taken from PCODES.DB
    '  >x.xx       :  data exceeded max for test
    '  T           :  too numerous to count
    '  ND          :  non-detect value, use detection limit in PCODES database
    '  NA, N/A, blank :  skip this value



    'HOUR-DAY FORMAT:

    'Line 1:  Keyword, File description....
    'Line 2:  STATION_ID, Month, Year, PCODE, CCODE, MULT
    'Line 3:  Column headings, typically: "HOUR,1,2,3,4,...,n" where n is 28-31
    'Line 4:  Hour, Value1, Value2, Value3, ..., Valuen
    '...line 4 is repeated 24 times, summary info below that is ignored
    'A QA check on number of columns versus month/year specified is performed

    'First non-commented line is assumed to be the file description (line 1 above).
    'The file description is intended to confirm you are using the correct file.

    'Line 2 includes a valid station identifier, the month (1-12) and year (19xx)
    'of the following hourly data, a daylight saving time flag, the PCode,
    'CCode (or blank), and multiplier

    'CCode on Line 2 will be applied to all values.
    'Blank CCodes will not be tolerated

    'The multiplier on Line 2 will be applied to all values.
    'Special multipliers are:

    '  blank       :  multiplier is not applied or is 1.0
    '  C2F         :  convert degrees C to degrees F
    '  F2C         :  convert degrees F to degrees C
    '  COEF2C      :  divide degrees F by 10 then convert to degrees C
    '                 (as found on DOE fixed format input)
    'for example, set the multiplier to 1.5472 to convert MGD to CFS.

    'Values in columns include the following special provisions:

    '  <x.xx       :  non-detect value, detection limit specified
    '  <           :  non-detect value, detection limit taken from PCODES.DB
    '  >x.xx       :  data exceeded max for test
    '  T           :  too numerous to count
    '  ND          :  non-detect value, use detection limit in PCODES database
    '  NA, N/A, blank  :  skip this value




    'DAY-MONTH FORMAT:

    'Line 1:  Keyword, File description
    'Line 2:  STATION_ID, Hour, Year, PCODE, CCODE, MULT
    'Line 3:  Column headings, typically: "DAY,JAN,FEB,MAR,...,DEC"
    'Line 4:  Day, ValueJan, ValueFeb, ValueMar, ..., ValueDec
    '...line 4 is repeated 31 times, summary info below that is ignored

    'First non-commented line is assumed to be the file description (line 1 above).
    'The file description is intended to confirm you are using the correct file.

    'Line 2 includes a valid station identifier, the hour and minute (hh:mm)
    '(Standard time) and year (yyyy) of the following daily data, the PCode,
    'CCode (or blank), and multiplier

    'CCode on Line 2 will be applied to all values.
    'Blank CCodes will not be tolerated

    'The multiplier on Line 2 will be applied to all values.
    'Special multipliers are:

    '  blank       :  multiplier is not applied or is 1.0
    '  C2F         :  convert degrees C to degrees F
    '  F2C         :  convert degrees F to degrees C
    '  COEF2C      :  divide degrees F by 10 then convert to degrees C
    '                 (as found on DOE fixed format input)
    'for example, set the multiplier to 1.5472 to convert MGD to CFS.

    'Values in columns include the following special provisions:

    '  <x.xx       :  non-detect value, detection limit specified
    '  <           :  non-detect value, detection limit taken from PCODES.DB
    '  >x.xx       :  data exceeded max for test
    '  T           :  too numerous to count
    '  ND          :  non-detect value, use detection limit in PCODES database
    '  NA, N/A, blank  :  skip this value




    'DATE-HOUR FORMAT:

    'Line 1:  Keyword, File description....
    'Line 2:  STATION_ID, PCODE, CCODE, MULT
    'Line 3:  Column headings, typically: "DATE,1,2,3,4,...,n" where n is 23:00 (hours for columns)
    'Line 4:  Date, Value1, Value2, Value3, ..., Valuen
    '...line 4 is repeated any number of times

    'First non-commented line is assumed to be the file description (line 1 above).
    'The file description is intended to confirm you are using the correct file.

    'Line 2 includes a valid station identifier of the following hourly data, the PCode,
    'CCode (or blank), and multiplier

    'CCode on Line 2 will be applied to all values.
    'Blank CCodes will not be tolerated

    'Values in columns include the following special provisions:

    '  <x.xx       :  non-detect value, detection limit specified
    '  <           :  non-detect value, detection limit taken from PCODES.DB
    '  >x.xx       :  data exceeded max for test
    '  T           :  too numerous to count
    '  ND          :  non-detect value, use detection limit in PCODES database
    '  NA, N/A, blank  :  skip this value




    'WRDB-CSV FORMAT:

    'This is just a comma-delimited export file format with no header info
    'Paradox could really just do the import, but WRDBImport does it so PCodes and
    'Station IDs, etc., will be validated. Each line is formatted as:

    'STATION_ID,DATE,TIME,PCODE,LEW_PCT,DEPTH_M,CCODE,RCODE,RESULT,VALIDATED,TRACK_ID

    'The validated flag will be kept as-is.


    'All input file formats will be converted to a form in the same order of the
    'DATA.DB database structure:

    'STATION_ID,DATE,TIME,PCODE,LEW_PCT,DEPTH_M,CCODE,RCODE,RESULT,VALIDATED,TRACK_ID

    'Paradox will set the database fields VALIDATE, and TRACK_ID to default values.

    'This conversion program will create an ASCII message file that can be read
    'or displayed indicating the success of the conversion. If successful,
    'the calling program will import the output file and run an INSERT query to add
    'the data to the specified working temporary database.

#Region "Public Enums"

    ''' <summary>
    ''' Detected format of imported file
    ''' </summary>
    ''' <remarks>0-3 are legacy WRDB text formats (which must have the keyword at the beginning)</remarks>
    Public Enum enumFormat
        Tabular
        HourDay
        DateHour
        DayMonth
        DataTable
        USGS
        PCS
        WQX
    End Enum

    ''' <summary>
    ''' When importing, indicates how many rows to skip between
    ''' </summary>
    Public Enum enumIncrement
        incrRow = 0
        incrTime = 1
    End Enum

    ''' <summary>
    ''' Type of delimiter separating fields in text file
    ''' </summary>
    Public Enum enumDelimiters
        delComma = 0
        delTab = 1
        delSpace = 2
        delOther = 3
    End Enum

    ''' <summary>
    ''' Indices for standard table names
    ''' </summary>
    Public Enum enumTables
        tDataTable = 0
        tStationTable = 7
        tPCodeTable = 8
        tCCodeTable = 9
        tRCodeTable = 10
        tQCodeTable = 11
        tBranchTable = 12
        tStationGroupTable = 13
        tPCodeGroupTable = 14
        tConvGroupTable = 15
        tAgencyTable = 16
        tFIPSCodes = 17
        tWQXMethods = 18
        tValidationTable = 19
        tMaxTable = 19 'always set to max value above; is max looping counter
    End Enum

    ''' <summary>
    ''' Indices for table names or header groups
    ''' </summary>
    Public Enum enumGroups
        gDataTable
        gDateFields
        gDayFields
        gHourFields
        gMonthFields
        gStoretFields
        gConvertFields

        'shared support tables
        gStationTable
        gPCodeTable
        gCCodeTable
        gRCodeTable
        gQCodeTable
        gBranchTable

        'project support tables
        gStationGroupTable
        gPCodeGroupTable
        gConvGroupTable

        gAgencyTable
        gFIPSCodeTable
        gWQXMethodTable
        gValidationTable

        'lists for dropdowns
        gStationList
        gPCodeList

        'special code
        gIgnore

        gMaxGroup = gIgnore 'always set to max value above; is max looping counter
        'when used for group numbers...
        gNotApplicable = -1
    End Enum

#End Region

#Region "Local Variables"
    Private mvarSkipPreview As Boolean
    Private mvarUseHeadings As Boolean
    Private mvarDescription As String
    Private mvarDelimiters As String
    Private mvarStationID As String
    Private mvarStationGrp As String
    Private mvarPCode As String
    Private mvarPCodeGrp As String
    Private mvarCCode As String
    Private mvarMultiplier As Double
    Private mvarAgency As String
    Private mvarNumCols As Integer
    Private mvarIncrement As Integer
    Private mvarIncrementType As enumIncrement
    Private mvarMonth As Integer
    Private mvarDay As Integer
    Private mvarYear As Integer
    Private mvarTime As String
    Private mvarLEW As String
    Private mvarDepth As String
    Private mvarDST As Boolean
    Private mvarColumns(250) As String
    Private mvarCCodes(250) As String
    Private mvarMultipliers(250) As String
    Private mvarConversionGroups(250) As String
    Private mvarFilters(250) As String
    Private mvarOtherDelimiter As String
    Private mvarValidateSupport As Boolean
    Private mvarRCodeConvGroup As String
    Private mvarPCodeConvGroup As String
    Private mvarNumRecordsImported As Integer
    Private mvarProceedWithImport As Boolean
    Private mvarBypassImport As Boolean '8/12/10: this was on options form but no longer needed (and confusing/dangerous)
    Private mvarAutoContinue As Boolean
    Private mvarAutoContinueDelay As Single
    Private mvarFormat As enumFormat
    Private mvarDataSource As WRDB.DataSource.clsData
    Private mvarBMDSegIndex As Integer
    Private mvarWDMDatasetIndex As Integer
    Private mvarWDMScenario As String
    Private mvarGridFont As Font
    Private mvarCultureName As String

    'added to support finding station info in header of USGS-NWIS/BASINS import file
    Private mvar_StationName As String
    Private mvar_StationType As String
    Private mvar_HUC As String
    Private mvar_Latitude As Double
    Private mvar_Longitude As Double
    Private mvar_DrArea As Double
    Private mvar_Agency As String
    Private mvar_PCodes As New Generic.Dictionary(Of String, String)

    Private cnImport As New Data.OleDb.OleDbConnection  'where import data coming from (cn may not be used is ASCII file)
    Private cmdImport As New Data.OleDb.OleDbCommand

    Private mvarImportFilename As String 'where import data coming from
    Private mvarDataTable As String 'where to append the WRDB-compatible imported data
    Private mvarPrivateDir As String 'directory where temporary files written
    Private mvarImportProgress As Double

    'these are public lists (separated by semicolons) of fields or values in support tables
    'used to fill drop down lists or check validity of input

    'following are initialized in class.initialize
    Private TableName(enumTables.tMaxTable) As String
    Private mvarDataFieldList As String
    Private mvarDateFieldList As String
    Private mvarDayList As String
    Private mvarHourList As String
    Private mvarMonthList As String
    Private mvarStoretFieldList As String
    Private mvarConvertFieldList As String
    Private mvarStationFieldList As String
    Private mvarPCodeFieldList As String
    Private mvarCCodeFieldList As String
    Private mvarRCodeFieldList As String
    Private mvarQCodeFieldList As String
    Private mvarBranchFieldList As String
    Private mvarStationGroupFieldList As String
    Private mvarPCodeGroupFieldList As String
    Private mvarConvGroupFieldList As String
    Private mvarAgencyFieldList As String
    Private mvarFIPSCodeFieldList As String
    Private mvarWQXMethodFieldList As String
    Private mvarValidationFieldList As String
    Private mvarIgnoreFieldList As String
    Private mvarAssumeSupport As Boolean

    'following are initialized when supportdir is set
    Private ValidList(enumTables.tMaxTable) As System.Text.StringBuilder

    ''' <summary>
    ''' Contains all valid headings for columns by category as semi-colon separated lists
    ''' (it will be easy and fast to do string search for valid entries)
    ''' </summary>
    Private ValidHeading(enumGroups.gMaxGroup) As String

    ''' <summary>
    ''' Stores mapping information for each column being imported
    ''' </summary>
    Private Structure FieldMappingType
        ''' <summary>Pointer to type of table to write to (data, stations, etc.)</summary>
        Dim TableNum As enumTables
        ''' <summary>Pointer to field in that table (date_time)</summary>
        Dim FieldNum As enumFields
        ''' <summary>Pointer to special header group that requires additional processing (storet); 0 if not applicable</summary>
        Dim GroupNum As enumGroups
        ''' <summary>Pointer to item in that special group (pcodestoret)</summary>
        Dim ItemNum As enumFields
    End Structure

    ''' <summary>One for each column in import file</summary>
    Private arFieldMapping(250) As FieldMappingType

    ''' <summary>One for each field in data table being written to</summary>
    Private arExportData(enumTables.tMaxTable, 30) As String

    ''' <summary>One for each field in data table being written to</summary>
    Private arFieldWidth(enumTables.tMaxTable)() As Integer

    Private Structure structMinMax
        Dim Min As Double
        Dim Max As Double
    End Structure

    ''' <summary>Array of expected min and max results by PCode</summary>
    Private dcResultMinMax As New Generic.Dictionary(Of String, structMinMax)

    ''' <summary>Indices for field names within a given table (and the item numbers within a set of conversion functions)
    '''(number same as field numbers, but subtract one if access through recordset)</summary>
    Private Enum enumFields

        'data fields

        dStation_ID = 1
        dDate_Time = 2
        dPCode = 3
        dLEW_Pct = 4
        dDepth_M = 5
        dCCode = 6
        dSCode = 7
        dLCode = 8
        dQCode = 9
        dAgency = 10
        dRCode = 11
        dResult = 12
        dValidated = 13
        dCreate_Update = 14
        dTrack_ID = 15

        'station fields

        sStation_ID = 1
        sStation_Name = 2
        sStation_Type = 3
        sData_Freq = 4
        sLatitude = 5
        sLongitude = 6
        sElevation = 7
        sBranch_ID = 8
        sRiver_Mile = 9
        sDr_Area = 10
        sAgency = 11
        sRef_ID = 12
        sRemarks = 13
        sOther = 14
        sConvert_Station_ID = 15
        sHUC = 16
        sReach_ID = 17
        sWaterBody = 18
        sBasin_Name = 19
        sCounty_Name = 20
        sEcoRegion = 21
        sPlanRegion = 22
        sFIPS_Code = 23
        sState = 24
        sCreate_Update = 25

        'pcode fields

        pPCode = 1
        pAnal_Name = 2
        pUnits = 3
        pTest_Method = 4
        pDet_Limit = 5
        pSig_Figs = 6
        pMin_Limit = 7
        pMax_Limit = 8
        pMin_Criteria = 9
        pMax_Criteria = 10
        pFormat_Str = 11
        pConvert_PCode = 12
        pConvert_Units = 13
        pConvert_Mult = 14
        pWQX_CharacteristicName = 15
        pWQX_ResultSampFraction = 16
        pWQX_ResultMeasureUnit = 17
        pWQX_AnalyticalMethodIDContext = 18
        pWQX_AnalyticalMethodID = 19
        pWQX_AnalyticalMethodName = 20
        pWQX_AnalyticalMethodQualifType = 21
        pWQX_AnalyticalMethodDesc = 22
        pCreate_Update = 23

        'ccode fields

        cCCode = 1
        cCCode_Name = 2
        cDescription = 3
        cConvert_CCode = 4

        'rcode fields

        rRCode = 1
        rDescription = 2
        rConvert_RCode = 3
        rCalc_Stats = 4
        rNondetect_PCode = 5
        rNondetect_Result = 6
        rDetect_Mult = 7

        'qcode fields

        qQCode = 1
        qQCode_Name = 2
        qDescription = 3

        'station group fields

        sgStationGroup = 1
        sgStation_ID = 2

        'pcode group fields

        pgPCodeGroup = 1
        pgPCode = 2

        'conversion group fields

        cgConvGroup = 1
        cgConvFrom = 2
        cgConvTo = 3

        'branches fields

        bBranch_ID = 1
        bBranch_Name = 2
        bDS_Branch_ID = 3
        bDS_Branch_RM = 4

        'agency fields

        aAgency = 1
        aAgency_Name = 2
        aDescription = 3

        'FIPS codes fields

        fFIPS_Code = 1
        fCounty_Name = 2
        fState = 3

        'WQX methods fields

        wWQX_AnalyticalMethodIDContext = 1
        wWQX_AnalyticalMethodID = 2
        wWQX_AnalyticalMethodName = 3
        wWQX_AnalyticalMethodQualifType = 4
        wWQX_AnalyticalMethodDesc = 5

        'validation table fields 

        vField_Name = 1
        vValid_Value = 2
        vDescription = 3

        'date & time fields

        tDate_Time = 1
        tDate = 2
        tTime = 3
        tTime_DST = 4
        tJulian = 5
        tDate_Time_Comb = 6
        tDate_YMD = 7
        tYear = 8
        tMonth = 9
        tDay = 10
        tHour = 11
        tMinute = 12

        'conversion fields

        oPCode_Storet = 1
        oRCode_Storet = 2
        oStation_ID_Storet = 3
        oAgency_Storet = 4
        oUnits_Storet = 5

    End Enum

    Private Structure ResultType
        Dim Result As String
        Dim Mult As String
        Dim CCode As String
        Dim PCode As String
        Dim _Month As Short
        Dim _Day As Short
        Dim _Hour As Short
    End Structure

    Public ConvertGroup As clsConvertGroup

    Private BlankTimeFound As Boolean
    Private NumStoretIgnored As Integer
    Private NumWarnings As Integer

    'text files where imported data will be written and later appended by WRDB
    Private filSupport(enumTables.tMaxTable) As Short

    Private Table, constr, Password As String
    Private RecNum As Integer 'global counter during import
    Private ImportSupport As Boolean 'if no data fields, will offer to just import support tables
    Private NumSupportRecordsAdded As Integer
    Friend printDoc As Drawing.Printing.PrintDocument

#End Region

#Region "Public Properties"

    ''' <summary>Set to True to bypass preview and immediate perform import operation</summary>
    Public Property SkipPreview() As Boolean
        Get
            SkipPreview = mvarSkipPreview
        End Get
        Set(ByVal Value As Boolean)
            mvarSkipPreview = Value
        End Set
    End Property

    ''' <summary>Accumulated the number of records imported</summary>
    Public ReadOnly Property NumRecordsImported() As Integer
        Get
            NumRecordsImported = mvarNumRecordsImported
        End Get
    End Property

    ''' <summary>Current progress, expressed as percent complete</summary>
    Public ReadOnly Property ImportProgress() As Double
        Get
            ImportProgress = mvarImportProgress
        End Get
    End Property

    ''' <summary>Detected format of file being imported</summary>
    Friend Property DataFormat() As enumFormat
        Get
            DataFormat = mvarFormat
        End Get
        Set(ByVal Value As enumFormat)
            mvarFormat = Value
        End Set
    End Property

    Friend Property WRDBDataSource() As WRDB.DataSource.clsData
        Get
            Return mvarDataSource
        End Get
        Set(ByVal value As WRDB.DataSource.clsData)
            mvarDataSource = value
        End Set
    End Property

    ''' <summary>
    ''' Name of tab-delimited data table containing imported data in tab-delimited format
    ''' Imported stations records are placed in private directory as Stations.txt, etc.
    ''' </summary>
    ''' <value></value>
    ''' <returns></returns>
    ''' <remarks>Can append data directly to a paradox format table in WRDB format, but
    '''ADO is pretty SLOW to add records, because it must maintain a primary key index (and BDE?)
    '''Have also tried other DB formats (TXT, Excel, and MDB linked to DB--all are slow except
    '''native MDB table)
    '''
    '''Alternative method is to write to ASCII file and user Paradox to quickly import it
    '''But, must be very careful to correctly import data/time values--BDE settings for date/time
    '''format must be consistent with format being used here.
    '''
    '''Another alternative, don'''t use Paradox import feature, instead read lines and interpret
    '''one at a time (at least you'd get progress information!)
    '''
    '''So.... if pass here a filename, assumes you want a tab-delimited
    '''file for import and date format is mm/dd/yyyy hh:mm
    '''</remarks>
    Public Property DataTable() As String
        Get
            DataTable = mvarDataTable
        End Get
        Set(ByVal Value As String)
            mvarDataTable = Value
        End Set
    End Property

    ''' <summary>Name of file to import </summary>
    Public Property ImportFilename() As String
        Get
            ImportFilename = mvarImportFilename
        End Get
        Set(ByVal Value As String)
            mvarImportFilename = Value
            SetDataSource()
        End Set
    End Property

    ''' <summary>
    ''' This is called when the import filename and delimiters are set
    ''' </summary>
    Private Sub SetDataSource()
        If mvarImportFilename = "" Or mvarImportFilename.Contains(";") Then Exit Sub
        If mvarDataSource IsNot Nothing Then mvarDataSource.Dispose()

        mvarFormat = enumFormat.DataTable
        Dim sheetname As String = ""

        Dim ext As String = IO.Path.GetExtension(mvarImportFilename).ToUpper

        'if is excel file, see if is legacy transfer file format; if so, export to text and import that way
        Select Case ext
            Case ".XLS", ".XLSX", ".XLSM"
                If Not My.Computer.FileSystem.DirectoryExists(WRDB.Project.clsProject.TempPath) Then My.Computer.FileSystem.CreateDirectory(WRDB.Project.clsProject.TempPath)
                Dim txtfile As String = WRDB.Project.clsProject.TempPath & "\" & IO.Path.GetFileNameWithoutExtension(mvarImportFilename) & ".txt"
                Dim xl As New WRDB.MSExcel.clsExcel(mvarImportFilename)
                If xl.SheetNames.Count = 1 Then
                    sheetname = xl.SheetNames(0)
                Else
                    With New frmSelect(False)
                        .Text = "Select Desired Sheet"
                        .lstItems.Items.AddRange(xl.SheetNames.ToArray)
                        If .ShowDialog = DialogResult.Cancel Then Exit Sub
                        sheetname = .lstItems.Text
                    End With
                End If
                xl.SaveAsText(sheetname, txtfile)
                xl.Close()
                Dim sr As New IO.StreamReader(txtfile)
                Dim fmt As String = sr.ReadLine.ToUpper
                If fmt.StartsWith("TABULAR") Or fmt.StartsWith("HOURDAY") Or fmt.StartsWith("DATEHOUR") Or fmt.StartsWith("DAYMONTH") Then
                    mvarImportFilename = txtfile
                    ext = ".TXT"
                End If
                sr.Close()
                sr.Dispose()
        End Select

        Select Case ext
            Case ".MDB", ".DB", ".DBF", ".BMD", ".DSS", ".DAT"
                mvarDataSource = WRDB.DataSource.clsData.Instantiate(mvarImportFilename)
            Case ".XLS", ".XLSX", ".XLSM"
                mvarDataSource = WRDB.DataSource.clsData.Instantiate(mvarImportFilename)
                mvarDataSource.ActiveTable = sheetname
            Case ".OUT", ".AIR"
                mvarDataSource = WRDB.DataSource.clsData.Instantiate(mvarImportFilename)
                If WarningMsg("Note that all LSPC files in the selected folder will be imported and you must explicitly map each LSPC column to the desired PCode. You may want to use the more powerful LSPC/EFDC import tool available on the Edit | Import menu which will you choose selected stations and PCodes, map them to values used in this Project, and perform filtering.\n\nDo you want to continue with import anyway?") = DialogResult.Cancel Then
                    mvarDataSource = Nothing
                    Exit Sub
                End If
            Case ".WDM"
                mvarDataSource = New WRDB.DataSource.clsDataWDM
                mvarDataSource.Initialize(mvarImportFilename)
                If mvarPCodeConvGroup <> "(None)" Then
                    For i As Integer = 0 To mvarDataSource.FieldNames.Count - 1
                        If mvarDataSource.FieldNames(i) = "PCode" Then mvarConversionGroups(i) = mvarPCodeConvGroup
                    Next
                End If
            Case Else
                'try to open text file
                Dim sr As New IO.StreamReader(mvarImportFilename)
                Dim fmt As String = ""
                If Not sr.EndOfStream Then
                    fmt = sr.ReadLine.ToUpper
                End If
                sr.Close()
                If fmt.StartsWith("TABULAR", StringComparison.OrdinalIgnoreCase) Then
                    mvarFormat = enumFormat.Tabular
                    mvarDataSource = New WRDB.DataSource.clsDataText
                    CType(mvarDataSource, WRDB.DataSource.clsDataText).Initialize(mvarImportFilename, vbTab, False, False)
                ElseIf fmt.StartsWith("HOURDAY", StringComparison.OrdinalIgnoreCase) Then
                    mvarFormat = enumFormat.HourDay
                    mvarDataSource = New WRDB.DataSource.clsDataText
                    CType(mvarDataSource, WRDB.DataSource.clsDataText).Initialize(mvarImportFilename, vbTab, False, False)
                ElseIf fmt.StartsWith("DATEHOUR", StringComparison.OrdinalIgnoreCase) Then
                    mvarFormat = enumFormat.DateHour
                    mvarDataSource = New WRDB.DataSource.clsDataText
                    CType(mvarDataSource, WRDB.DataSource.clsDataText).Initialize(mvarImportFilename, vbTab, False, False)
                ElseIf fmt.StartsWith("DAYMONTH", StringComparison.OrdinalIgnoreCase) Then
                    mvarFormat = enumFormat.DayMonth
                    mvarDataSource = New WRDB.DataSource.clsDataText
                    CType(mvarDataSource, WRDB.DataSource.clsDataText).Initialize(mvarImportFilename, vbTab, False, False)
                ElseIf fmt.StartsWith("<WQX>", StringComparison.OrdinalIgnoreCase) Then
                    mvarFormat = enumFormat.WQX
                    mvarDataSource = New WRDB.DataSource.clsDataStoretXML
                    CType(mvarDataSource, WRDB.DataSource.clsDataStoretXML).Initialize(mvarImportFilename)
                Else
                    'see if is USGS download file, which will always have commented header block and will contain URL
                    sr = New IO.StreamReader(mvarImportFilename)
                    If Not sr.EndOfStream Then
                        Dim s As String = sr.ReadLine
                        Do
                            If IO.Path.GetExtension(mvarImportFilename).ToLower = ".rdb" OrElse (fmt.StartsWith("#") And s.Contains("usgs.gov")) Then
                                mvarFormat = enumFormat.USGS
                                mvarUseHeadings = True
                                mvarDelimiters = vbTab
                                Exit Do
                            End If
                            s = sr.ReadLine
                        Loop Until sr.EndOfStream OrElse Not s.StartsWith("#")
                    End If
                    sr.Close()
                    sr.Dispose()
                    sr = Nothing
                    If mvarFormat <> enumFormat.USGS Then 'see if first row has non-numeric first column (to know whether to assume has headings)
                        If Val(fmt) = 0 Then
                            mvarUseHeadings = True
                        End If
                    End If
                    mvarDataSource = New WRDB.DataSource.clsDataText
                    CType(mvarDataSource, WRDB.DataSource.clsDataText).Initialize(mvarImportFilename, mvarDelimiters, mvarUseHeadings, mvarFormat = enumFormat.DataTable Or mvarFormat = enumFormat.USGS)
                End If
        End Select
        mvarNumCols = mvarDataSource.FieldNames.Count
    End Sub

    ''' <summary>If true, will assume that the file to be imported is a Support table</summary>
    Public Property AssumeSupport() As Boolean
        Get
            AssumeSupport = mvarAssumeSupport
        End Get
        Set(ByVal Value As Boolean)
            mvarAssumeSupport = Value
        End Set
    End Property

    ''' <summary>
    ''' Name of temporary folder where working files are stored
    ''' </summary>
    Public Property PrivateDir() As String
        Get
            If mvarPrivateDir = "" Then
                mvarPrivateDir = My.Computer.FileSystem.SpecialDirectories.Temp & "\WRDB\"
                If Not My.Computer.FileSystem.DirectoryExists(mvarPrivateDir) Then My.Computer.FileSystem.CreateDirectory(mvarPrivateDir)
                mvarPrivateDir &= "Import\"
                If Not My.Computer.FileSystem.DirectoryExists(mvarPrivateDir) Then My.Computer.FileSystem.CreateDirectory(mvarPrivateDir)
            End If
            Return mvarPrivateDir
        End Get
        Set(ByVal Value As String)
            'add trailing backslash if not present
            If Not Value.EndsWith("\") Then Value &= "\"
            If Not My.Computer.FileSystem.DirectoryExists(mvarPrivateDir) Then My.Computer.FileSystem.CreateDirectory(mvarPrivateDir)
            mvarPrivateDir = Value
        End Set
    End Property

#End Region

#Region "Friend Properties"

    ''' <summary>
    ''' Names of all data table fields (semi-colon separated)
    ''' </summary>
    Friend ReadOnly Property DataFieldList() As String
        Get
            Return mvarDataFieldList
        End Get
    End Property

    ''' <summary>
    ''' List of valid column heading names pertaining to dates and times
    ''' </summary>
    Friend ReadOnly Property DateFieldList() As String
        Get
            Return mvarDateFieldList
        End Get
    End Property

    ''' <summary>
    ''' List of valid column heading names pertaining to day of month
    ''' </summary>
    Friend ReadOnly Property DayList() As String
        Get
            Return mvarDayList
        End Get
    End Property

    ''' <summary>
    ''' List of valid column heading names pertaining to hour of day
    ''' </summary>
    Friend ReadOnly Property HourList() As String
        Get
            Return mvarHourList
        End Get
    End Property

    ''' <summary>
    ''' List of valid column heading names pertaining to month of year
    ''' </summary>
    Friend ReadOnly Property MonthList() As String
        Get
            Return mvarMonthList
        End Get
    End Property

    ''' <summary>
    ''' List of valid column heading names pertaining to storet field conversions
    ''' </summary>
    Friend ReadOnly Property StoretFieldList() As String
        Get
            Return mvarStoretFieldList
        End Get
    End Property

    ''' <summary>
    ''' List of valid column heading names pertaining to support key field conversions
    ''' </summary>
    Friend ReadOnly Property ConvertFieldList() As String
        Get
            Return mvarConvertFieldList
        End Get
    End Property

    ''' <summary>
    ''' List of valid column heading names including all station ids for project
    ''' </summary>
    Friend ReadOnly Property StationFieldList() As String
        Get
            Return mvarStationFieldList
        End Get
    End Property

    ''' <summary>
    ''' List of valid column heading names including all pcodes for project
    ''' </summary>
    Friend ReadOnly Property PCodeFieldList() As String
        Get
            Return mvarPCodeFieldList
        End Get
    End Property

    ''' <summary>
    ''' List of valid column heading names including all ccodes for project
    ''' </summary>
    Friend ReadOnly Property CCodeFieldList() As String
        Get
            Return mvarCCodeFieldList
        End Get
    End Property

    ''' <summary>
    ''' List of valid column heading names including all rcodes for project
    ''' </summary>
    Friend ReadOnly Property RCodeFieldList() As String
        Get
            Return mvarRCodeFieldList
        End Get
    End Property

    ''' <summary>
    ''' List of valid column heading names including all ccodes for project
    ''' </summary>
    Friend ReadOnly Property QCodeFieldList() As String
        Get
            Return mvarQCodeFieldList
        End Get
    End Property

    ''' <summary>
    ''' List of valid column heading names including all branch IDs for project
    ''' </summary>
    Friend ReadOnly Property BranchFieldList() As String
        Get
            Return mvarBranchFieldList
        End Get
    End Property

    ''' <summary>
    ''' List of valid column heading names including all station groups for project
    ''' </summary>
    Friend ReadOnly Property StationGroupFieldList() As String
        Get
            Return mvarStationGroupFieldList
        End Get
    End Property

    ''' <summary>
    ''' List of valid column heading names including all pcode groups for project
    ''' </summary>
    Friend ReadOnly Property PCodeGroupFieldList() As String
        Get
            Return mvarPCodeGroupFieldList
        End Get
    End Property

    ''' <summary>
    ''' List of valid column heading names including all conversion groups for project
    ''' </summary>
    Friend ReadOnly Property ConvGroupFieldList() As String
        Get
            Return mvarConvGroupFieldList
        End Get
    End Property

    ''' <summary>
    ''' List of valid column heading names for agency table
    ''' </summary>
    Friend ReadOnly Property AgencyFieldList() As String
        Get
            Return mvarAgencyFieldList
        End Get
    End Property

    ''' <summary>
    ''' List of valid column heading names 
    ''' </summary>
    Friend ReadOnly Property FIPSCodeFieldList() As String
        Get
            Return mvarFIPSCodeFieldList
        End Get
    End Property

    ''' <summary>
    ''' List of valid column heading names 
    ''' </summary>
    Friend ReadOnly Property WQXMethodFieldList() As String
        Get
            Return mvarWQXMethodFieldList
        End Get
    End Property

    ''' <summary>
    ''' List of valid column heading names for validation table
    ''' </summary>
    Friend ReadOnly Property ValidationFieldList() As String
        Get
            Return mvarValidationFieldList
        End Get
    End Property

    ''' <summary>
    ''' List of valid column heading names: IGNORE
    ''' </summary>
    Friend ReadOnly Property IgnoreFieldList() As String
        Get
            Return mvarIgnoreFieldList
        End Get
    End Property

    ''' <summary>
    ''' True if preview was acceptable and is OK to proceed with the full import
    ''' </summary>
    Friend Property ProceedWithImport() As Boolean
        Get
            Return mvarProceedWithImport
        End Get
        Set(ByVal Value As Boolean)
            mvarProceedWithImport = Value
        End Set
    End Property

    ''' <summary>
    ''' True if is text (non-table) file and want to interpret first row as column headings
    ''' </summary>
    Public Property UseHeadings() As Boolean
        Get
            Return mvarUseHeadings
        End Get
        Set(ByVal Value As Boolean)
            mvarUseHeadings = Value
            If TypeOf mvarDataSource Is WRDB.DataSource.clsDataText Then
                CType(mvarDataSource, WRDB.DataSource.clsDataText).Reopen(mvarDelimiters, mvarUseHeadings)
                'ElseIf TypeOf mvarDataSource Is WRDB.DataSource.clsDataExcel Then
                '    mvarDataSource.Rewind(mvarUseHeadings)
            End If
        End Set
    End Property

    ''' <summary>
    ''' Description of this imported dataset
    ''' </summary>
    Friend Property Description() As String
        Get
            Return mvarDescription
        End Get
        Set(ByVal Value As String)
            mvarDescription = Value
        End Set
    End Property

    ''' <summary>
    ''' Global StationID to use for all imported records
    ''' </summary>
    Friend Property StationID() As String
        Get
            Return mvarStationID
        End Get
        Set(ByVal Value As String)
            mvarStationID = Value
        End Set
    End Property

    ''' <summary>
    ''' Global StationGroup to use for all imported records
    ''' </summary>
    Friend Property StationGroup() As String
        Get
            Return mvarStationGrp
        End Get
        Set(ByVal Value As String)
            mvarStationGrp = Value
        End Set
    End Property

    ''' <summary>
    ''' Global PCode to use for all imported records
    ''' </summary>
    Friend Property PCode() As String
        Get
            Return mvarPCode
        End Get
        Set(ByVal Value As String)
            mvarPCode = Value
        End Set
    End Property

    ''' <summary>
    ''' Global PCode group to use for all imported records
    ''' </summary>
    Friend Property PCodeGroup() As String
        Get
            Return mvarPCodeGrp
        End Get
        Set(ByVal Value As String)
            mvarPCodeGrp = Value
        End Set
    End Property

    ''' <summary>
    ''' Global CCode to use for all imported records
    ''' </summary>
    Friend Property CCode() As String
        Get
            Return mvarCCode
        End Get
        Set(ByVal Value As String)
            mvarCCode = Value
        End Set
    End Property

    ''' <summary>
    ''' Global Agency to use for all imported records
    ''' </summary>
    Friend Property DataAgency() As String
        Get
            Return mvarAgency
        End Get
        Set(ByVal Value As String)
            mvarAgency = Value
        End Set
    End Property

    ''' <summary>
    ''' Global Multiplier to use for all imported records
    ''' </summary>
    Friend Property Multiplier() As Double
        Get
            Return mvarMultiplier
        End Get
        Set(ByVal Value As Double)
            mvarMultiplier = Value
        End Set
    End Property

    ''' <summary>
    ''' Global distance from left edge of water to use for all imported records
    ''' </summary>
    Friend Property LEW_Pct() As String
        Get
            Return mvarLEW
        End Get
        Set(ByVal Value As String)
            mvarLEW = Value
        End Set
    End Property

    ''' <summary>
    ''' Global depth in meters to use for all imported records
    ''' </summary>
    Friend Property Depth_M() As String
        Get
            Return mvarDepth
        End Get
        Set(ByVal Value As String)
            mvarDepth = Value
        End Set
    End Property

    ''' <summary>
    ''' Number of column detected in import file
    ''' </summary>
    Friend Property NumCols() As Integer
        Get
            Return mvarNumCols
        End Get
        Set(ByVal Value As Integer)
            mvarNumCols = Value
        End Set
    End Property

    ''' <summary>
    ''' Row or hour increment (for skipping some imported records)
    ''' </summary>
    Friend Property Increment() As Integer
        Get
            Return mvarIncrement
        End Get
        Set(ByVal Value As Integer)
            mvarIncrement = Value
        End Set
    End Property

    ''' <summary>
    ''' Type of increment (row or hour)
    ''' </summary>
    Friend Property IncrementType() As enumIncrement
        Get
            Return mvarIncrementType
        End Get
        Set(ByVal Value As enumIncrement)
            mvarIncrementType = Value
        End Set
    End Property

    ''' <summary>
    ''' Imported time for current import record
    ''' </summary>
    Friend Property DataTime() As String
        Get
            Return mvarTime
        End Get
        Set(ByVal Value As String)
            mvarTime = Value
        End Set
    End Property

    ''' <summary>
    ''' Imported month for current import record
    ''' </summary>
    Friend Property DataMonth() As Integer
        Get
            Return mvarMonth
        End Get
        Set(ByVal Value As Integer)
            mvarMonth = Value
        End Set
    End Property

    ''' <summary>
    ''' Imported day of month for current import record
    ''' </summary>
    Friend Property DataDay() As Integer
        Get
            Return mvarDay
        End Get
        Set(ByVal Value As Integer)
            mvarDay = Value
        End Set
    End Property

    ''' <summary>
    ''' Imported year for current import record
    ''' </summary>
    Friend Property DataYear() As Integer
        Get
            Return mvarYear
        End Get
        Set(ByVal Value As Integer)
            mvarYear = Value
        End Set
    End Property

    ''' <summary>
    ''' Imported daylight saving time flag for current import record
    ''' </summary>
    Friend Property DST() As Boolean
        Get
            Return mvarDST
        End Get
        Set(ByVal Value As Boolean)
            mvarDST = Value
        End Set
    End Property

    ''' <summary>
    ''' Global setting for field delimiters for non-table import files
    ''' </summary>
    Friend Property OtherDelimiter() As String
        Get
            Return mvarOtherDelimiter
        End Get
        Set(ByVal Value As String)
            mvarOtherDelimiter = Value
        End Set
    End Property

    ''' <summary>
    ''' Mapped column type associated with each column to be imported
    ''' </summary>
    ''' <param name="Index">Column index (note: is 0-based!)</param>
    Friend Property Columns(ByVal Index As Integer) As String
        Get
            Return mvarColumns(Index)
        End Get
        Set(ByVal Value As String)
            mvarColumns(Index) = Value
        End Set
    End Property

    ''' <summary>
    ''' CCodes associated with with each column to be imported
    ''' </summary>
    ''' <param name="Index">Column index (note: is 0-based!)</param>
    Friend Property CCodes(ByVal Index As Integer) As String
        Get
            Return mvarCCodes(Index)
        End Get
        Set(ByVal Value As String)
            mvarCCodes(Index) = Value
        End Set
    End Property

    ''' <summary>
    ''' Multiplier number or code associated with with each column to be imported
    ''' </summary>
    ''' <param name="Index">Column index (note: is 0-based!)</param>
    Friend Property Multipliers(ByVal Index As Integer) As String
        Get
            Return mvarMultipliers(Index)
        End Get
        Set(ByVal Value As String)
            mvarMultipliers(Index) = Value
        End Set
    End Property

    ''' <summary>
    ''' Conversion group name associated with with each column to be imported
    ''' </summary>
    ''' <param name="Index">Column index (note: is 0-based!)</param>
    Friend Property ConversionGroups(ByVal Index As Integer) As String
        Get
            Return mvarConversionGroups(Index)
        End Get
        Set(ByVal Value As String)
            mvarConversionGroups(Index) = Value
        End Set
    End Property

    ''' <summary>
    ''' Filters associated with with each column to be imported
    ''' </summary>
    ''' <param name="Index">Column index (note: is 0-based!)</param>
    Friend Property Filters(ByVal Index As Integer) As String
        Get
            Return mvarFilters(Index)
        End Get
        Set(ByVal Value As String)
            mvarFilters(Index) = Value
        End Set
    End Property

    ''' <summary>
    ''' List of available column delimiters (all placed in a single string)
    ''' </summary>
    Public Property Delimiters() As String
        Get
            Return mvarDelimiters
        End Get
        Set(ByVal Value As String)
            If Value <> mvarDelimiters Then
                mvarDelimiters = Value
                If mvarDataSource.DataType = DataSource.clsData.enumDataType.Text Then
                    CType(mvarDataSource, WRDB.DataSource.clsDataText).Reopen(mvarDelimiters, mvarUseHeadings)
                End If
            End If
        End Set
    End Property

    ''' <summary>
    ''' True if you want to validate imported data fields against their associated list of valid key values
    ''' </summary>
    Public Property ValidateSupport() As Boolean
        Get
            Return mvarValidateSupport
        End Get
        Set(ByVal Value As Boolean)
            mvarValidateSupport = Value
        End Set
    End Property

    ''' <summary>
    ''' True if you want to convert imported RCode values using conversion group (when prepended to Result)
    ''' </summary>
    Friend Property RCodeConvGroup() As String
        Get
            Return mvarRCodeConvGroup
        End Get
        Set(ByVal Value As String)
            mvarRCodeConvGroup = Value
        End Set
    End Property

    ''' <summary>
    ''' Returns semicolon separated list of valid field values for key fields in specified table
    ''' </summary>
    ''' <param name="TableNum">Standard table index</param>
    Friend ReadOnly Property ValidValueList(ByVal TableNum As enumTables) As String
        Get
            If ValidList(TableNum) Is Nothing Then Return "" Else Return ValidList(TableNum).ToString
        End Get
    End Property

    ''' <summary>
    ''' Determine if a key value is contained in the specified valid field list
    ''' </summary>
    ''' <param name="TableNum">Standard table index</param>
    ''' <param name="TestValue">Value to search for</param>
    ''' <returns>True if found</returns>
    Friend Function FieldListContains(ByVal TableNum As enumTables, ByVal TestValue As String) As Boolean
        If ValidList(TableNum) Is Nothing Then
            Return False
        Else
            Return (";" & ValidList(TableNum).ToString & ";").Contains(";" & TestValue & ";")
        End If
    End Function

    ''' <summary>
    ''' Add new item to valid field list if not found
    ''' </summary>
    ''' <param name="TableNum">Standard table index</param>
    ''' <param name="TestValue">Value to search for</param>
    ''' <remarks></remarks>
    Friend Sub FieldListAdd(ByVal TableNum As enumTables, ByVal TestValue As String)
        If Not FieldListContains(TableNum, TestValue) Then ValidList(TableNum).Append(";" & TestValue)
    End Sub

    ''' <summary>
    ''' If true, assume that the import file is in the perfect format for a direct import
    ''' </summary>
    Friend Property BypassImport() As Boolean
        Get
            Return mvarBypassImport
        End Get
        Set(ByVal Value As Boolean)
            mvarBypassImport = Value
        End Set
    End Property

    ''' <summary>
    ''' Name of culture info setting to use for imported data
    ''' </summary>
    Friend Property CultureName() As String
        Get
            Return mvarCultureName
        End Get
        Set(ByVal value As String)
            mvarCultureName = value
        End Set
    End Property

    ''' <summary>
    ''' Whether data should automatically be imported
    ''' </summary>
    Public Property AutoContinue() As Boolean
        Get
            Return mvarAutoContinue
        End Get
        Set(ByVal value As Boolean)
            mvarAutoContinue = value
        End Set
    End Property

    ''' <summary>
    ''' Increment for date to use in data entry form
    ''' </summary>
    Public Property AutoIncrement As String = "No Auto Increment"

    ''' <summary>
    ''' Number of seconds to delay before auto-continue in progress form
    ''' </summary>
    Public Property AutoContinueDelay() As Single
        Get
            Return mvarAutoContinueDelay
        End Get
        Set(ByVal value As Single)
            mvarAutoContinueDelay = value
        End Set
    End Property

    Friend Property WDMDataSetIndex() As Integer
        Get
            Return mvarWDMDatasetIndex
        End Get
        Set(ByVal value As Integer)
            mvarWDMDatasetIndex = value
        End Set
    End Property

    ''' <summary>
    ''' This has been added for ImportGIS which may do batch import of WDM data and may want to import only computed or observed
    ''' </summary>
    Public Property WDMScenario() As String
        Get
            Return mvarWDMScenario
        End Get
        Set(ByVal value As String)
            mvarWDMScenario = value
        End Set
    End Property

    ''' <summary>
    ''' This has been added for ImportGIS which may do batch import of WDM data and may want to convert pcodes on import
    ''' </summary>
    Public Property PCodeConvGroup() As String
        Get
            Return mvarPCodeConvGroup
        End Get
        Set(ByVal value As String)
            mvarPCodeConvGroup = value
        End Set
    End Property

    Friend Property BMDSegIndex() As Integer
        Get
            Return mvarBMDSegIndex
        End Get
        Set(ByVal value As Integer)
            mvarBMDSegIndex = value
        End Set
    End Property

    Friend Property GridFont() As Font
        Get
            Return mvarGridFont
        End Get
        Set(ByVal value As Font)
            mvarGridFont = value
        End Set
    End Property

    Friend Property StationName() As String
        Get
            Return mvar_StationName
        End Get
        Set(ByVal value As String)
            mvar_StationName = value
        End Set
    End Property

    Friend Property StationType() As String
        Get
            Return mvar_StationType
        End Get
        Set(ByVal value As String)
            mvar_StationType = value
        End Set
    End Property

    Friend Property HUC() As String
        Get
            Return mvar_HUC
        End Get
        Set(ByVal value As String)
            mvar_HUC = value
        End Set
    End Property

    Friend Property Latitude() As Double
        Get
            Return mvar_Latitude
        End Get
        Set(ByVal value As Double)
            mvar_Latitude = value
        End Set
    End Property

    Friend Property Longitude() As Double
        Get
            Return mvar_Longitude
        End Get
        Set(ByVal value As Double)
            mvar_Longitude = value
        End Set
    End Property

    Friend Property DrArea() As Double
        Get
            Return mvar_DrArea
        End Get
        Set(ByVal value As Double)
            mvar_DrArea = value
        End Set
    End Property

    Friend Property Agency() As String
        Get
            Return mvar_Agency
        End Get
        Set(ByVal value As String)
            mvar_Agency = value
        End Set
    End Property

    Friend Property PCodes() As Generic.Dictionary(Of String, String)
        Get
            Return mvar_PCodes
        End Get
        Set(ByVal value As Generic.Dictionary(Of String, String))
            mvar_PCodes = value
        End Set
    End Property
#End Region

#Region "Public Methods"


    ''' <summary>
    ''' Set list of valid support table keys for later validation (will convert to upper case)
    ''' </summary>
    ''' <param name="TableNum">Standard table index</param>
    ''' <param name="vValidList">Semi-colon separated list of valid key items</param>
    Public Sub SetValidList(ByVal TableNum As enumTables, ByVal vValidList As String)
        'so WRDB can directly pass string with list of valid key fields
        ValidList(TableNum) = New System.Text.StringBuilder
        ValidList(TableNum).Append(vValidList.ToUpper)
        If TableNum = enumTables.tStationTable Then ValidHeading(enumGroups.gStationList) = ValidList(TableNum).ToString
        If TableNum = enumTables.tPCodeTable Then ValidHeading(enumGroups.gPCodeList) = ValidList(TableNum).ToString
    End Sub

    ''' <summary>
    ''' Set list of min and max result values associated with each pcode value used for result validation
    ''' </summary>
    ''' <param name="PCode">PCode</param>
    ''' <param name="Min">Minimum value</param>
    ''' <param name="Max">Maximum value</param>
    Public Sub SetValidPCodeLimits(ByVal PCode As String, ByVal Min As Double, ByVal Max As Double)
        Dim MinMax As structMinMax
        MinMax.Min = Min
        MinMax.Max = Max
        dcResultMinMax.Add(PCode, MinMax)
    End Sub

    ''' <summary>
    ''' Show the data entry form, allow multiple sets of data to be added to data file, then import all showing progress
    ''' </summary>
    Public Function ShowDataEntry(ByVal ParentForm As Form) As Boolean
        ProgressForm = New frmProgress
        With ProgressForm
            .WRDBImport = Me
            .Show(ParentForm)
            .BringToFront()
            PrintText("Note: these status messages are being appended to:")
            PrintText("<My Documents>\WRDB\Import Log.txt")
            PrintText()
        End With

        With New frmDataEntry(Me)
            mvarProceedWithImport = (.ShowDialog = DialogResult.OK And .SomeAdded)
            .Dispose()
        End With

        With ProgressForm
            If mvarProceedWithImport Then
                .DoImport()
                mvarProceedWithImport = (.DialogResult = DialogResult.OK)
                If mvarProceedWithImport Then 'append to log file
                    Dim path As String = My.Computer.FileSystem.SpecialDirectories.MyDocuments & "\WRDB"
                    If Not My.Computer.FileSystem.DirectoryExists(path) Then My.Computer.FileSystem.CreateDirectory(path)
                    Dim sw As New IO.StreamWriter(path & "\Import Log.txt", True)
                    sw.Write(.TextBox.Text.Replace(vbLf, vbCrLf))
                    sw.Close()
                    sw.Dispose()
                    sw = Nothing
                End If
            Else
                .Close()
                .Dispose()
            End If
        End With

        Return mvarProceedWithImport
    End Function


    ''' <summary>
    ''' Show the appropriate file import form (frmPreview for database and text import, frmPreviewDSS for DSS data)
    ''' This is done automatically if the import filename has already been provided, otherwise a file dialog will be shown
    ''' </summary>
    Public Function ShowPreview(ByVal ParentForm As Form) As Boolean
        Dim PreviewForm As frmPreviewData
        Dim ImportFiles() As String = Nothing

        If mvarImportFilename = "" Then 'do this so file dialog shows on top
            With New OpenFileDialog
                .Multiselect = True
                .Filter = "All Supported Formats|*.db;*.dbf;*.mdb;*.xls;*.xlsx;*.xlsm;*.txt;*.csv;*.rdb;*.bmd;*.dss;*.wdm;*.out;*.air;*.dat|Paradox (WRDB) Tables (*.db)|*.db|dBase Tables (*.dbf)|*.dbf|Access Database Files (*.mdb)|*.mdb|Excel Spreadsheet Files (*.xls)|*.xls|Excel 2007 Spreadsheet Files (*.xlsx)|*.xlsx|Excel 2007 Macro-Enabled Spreadsheet Files (*.xlsm)|*.xlsm|Tab-delimited Text Files (*.txt)|*.txt|Comma-delimited Text Files (*.csv)|*.csv|USGS/NWIS Text Files (*.rdb)|*.rdb|Binary Modeling Data Files (*.bmd)|*.bmd|COE Data Storage System Files (*.dss)|*.dss|Watershed Data Management Files (*.wdm)|*.wdm|LSPC Output Files (*.out)|*.out|LSPC Input Files (*.air)|*.air|EFDC Output Files (*.out)|*.out|YSI Binary Files (*.dat)|*.dat|All Files (*.*)|*.*"
                .FilterIndex = GetSetting(RegApp, "Settings", "FilterIndex", 0)
                .Title = "Open Import File"
                .InitialDirectory = GetSetting(RegApp, "Settings", "Directory", My.Computer.FileSystem.SpecialDirectories.MyDocuments & "\WRDB")
                If .ShowDialog = DialogResult.Cancel Then Return False
                'ImportFile = .FileName
                ImportFiles = .FileNames
                SaveSetting(RegApp, "Settings", "Directory", IO.Path.GetDirectoryName(ImportFiles(0)))
                SaveSetting(RegApp, "Settings", "FilterIndex", .FilterIndex)
                .Dispose()
            End With
        Else
            ImportFiles = mvarImportFilename.Split(";")
        End If

        ProgressForm = New frmProgress
        With ProgressForm
            .WRDBImport = Me
            .Show(ParentForm)
            .BringToFront()
        End With

        Dim HeaderWritten As Boolean = False

        For i As Integer = 0 To ImportFiles.Length - 1

            If ImportFiles.Length > 1 Then ProgressForm.Text = String.Format("WRDB Import Progress (File {0} of {1})", i + 1, ImportFiles.Length)
            ImportFilename = ImportFiles(i)
            If mvarDataSource Is Nothing Then Return False

            Dim j As Integer = InStr(mvarImportFilename, " [")
            Dim s As String
            If j <> 0 Then
                s = Left(mvarImportFilename, j - 1)
            Else
                s = mvarImportFilename
            End If
            If s = "" Or Not My.Computer.FileSystem.FileExists(s) Then Return False

            PreviewForm = New frmPreviewData(Me)

            If mvarSkipPreview Then
                mvarProceedWithImport = PreviewForm.ReadHeader(mvarSkipPreview)
                If ImportFiles.Length > 1 Then PreviewForm.btnLoadLast.PerformClick()
            Else
                PreviewForm.BringToFront()
                mvarProceedWithImport = (PreviewForm.ShowDialog() = DialogResult.OK)
                If mvarProceedWithImport And ImportFiles.Length > 1 And i = 0 Then
                    If MessageBox.Show("You have selected multiple files to import; to you want to bypass the preview form for each file, and use the just-specified import options and column assignments for all files (assuming that the format of all selected import files is identical)?", "WRDB Import", MessageBoxButtons.YesNo, MessageBoxIcon.Question) = DialogResult.Yes Then
                        mvarSkipPreview = True
                    End If
                End If
            End If

            PreviewForm.Dispose()
            PreviewForm = Nothing

            If mvarProceedWithImport Then
                Import(HeaderWritten)
                If ProgressForm.ImportCancelled Then Return False
                HeaderWritten = True
            Else
                ProgressForm.Close()
                ProgressForm.Dispose()
                Return False
            End If

            'If Not mvarProceedWithImport Then Return False
            'Return mvarProceedWithImport
        Next

        ProgressForm.TextBox.AppendText(vbCrLf & "Note: these status messages are being appended to:")
        ProgressForm.TextBox.AppendText(vbCrLf & "<My Documents>\WRDB\Import Log.txt")

        With ProgressForm
            If Not mvarProceedWithImport Then
                .Close()
                Return False
            End If
            .DoImport()
            mvarProceedWithImport = (.DialogResult = DialogResult.OK)
            If mvarProceedWithImport Then 'append to log file
                Dim path As String = My.Computer.FileSystem.SpecialDirectories.MyDocuments & "\WRDB"
                If Not My.Computer.FileSystem.DirectoryExists(path) Then My.Computer.FileSystem.CreateDirectory(path)
                Dim sw As New IO.StreamWriter(path & "\Import Log.txt", True)
                sw.Write(.TextBox.Text.Replace(vbLf, vbCrLf))
                sw.Close()
                sw.Dispose()
                sw = Nothing
            End If
        End With
        Return mvarProceedWithImport
    End Function


    Friend Sub Import(Optional ByVal AppendToOutput As Boolean = False)
        Dim swData As IO.StreamWriter = Nothing
        Try
            Dim i, j As Short
            Dim TableNum As enumTables
            Dim t1 As Single
            Dim arImportData() As String
            Dim StartTimer, LastTimer As Single
            Dim ar As Object
            Dim success As Boolean

            PrintText("Name of file imported:")
            PrintText(IIf(mvarImportFilename.Contains("Clipboard.txt"), "<CLIPBOARD>", mvarImportFilename))
            PrintText()

            mvarNumRecordsImported = 0
            StartTimer = VB.Timer()

            mvarDataSource.Rewind()

            If ValidList(enumTables.tStationTable) Is Nothing And ValidList(enumTables.tPCodeTable) Is Nothing Then mvarValidateSupport = False
            If mvarDataTable = "" Then mvarDataTable = PrivateDir & "Data.txt"

            If Not AppendToOutput Then
                If My.Computer.FileSystem.FileExists(mvarDataTable) Then My.Computer.FileSystem.DeleteFile(mvarDataTable)
                For TableNum = enumTables.tStationTable To enumTables.tMaxTable
                    If My.Computer.FileSystem.FileExists(PrivateDir & TableName(TableNum - 1) & ".txt") Then My.Computer.FileSystem.DeleteFile(PrivateDir & TableName(TableNum - 1) & ".txt")
                Next
            End If

            If mvarBypassImport Then 'assume import file is in the perfect format for direct import (8/12/10--will always be false now)

                If Not MapColumnsToFields(True) Then Exit Sub

                success = True

                'which one does it match?
                For TableNum = enumTables.tDataTable To enumTables.tMaxTable
                    If TableNum = enumTables.tDataTable Or (TableNum >= enumTables.tStationTable And TableNum <= enumTables.tMaxTable) Then
                        Dim MinField As enumFields = Choose(TableNum + 1, enumFields.dStation_ID, Nothing, Nothing, Nothing, Nothing, Nothing, Nothing, enumFields.sStation_ID, enumFields.pPCode, enumFields.cCCode, enumFields.rRCode, enumFields.qQCode, enumFields.bBranch_ID, enumFields.sgStationGroup, enumFields.pgPCodeGroup, enumFields.sgStationGroup, enumFields.pgPCodeGroup, enumFields.cgConvGroup, enumFields.aAgency, enumFields.fFIPS_Code, enumFields.wWQX_AnalyticalMethodIDContext, enumFields.vField_Name)
                        Dim MaxField As enumFields = Choose(TableNum + 1, enumFields.dTrack_ID, Nothing, Nothing, Nothing, Nothing, Nothing, Nothing, enumFields.sCreate_Update, enumFields.pCreate_Update, enumFields.cConvert_CCode, enumFields.rDetect_Mult, enumFields.qDescription, enumFields.bDS_Branch_RM, enumFields.sgStation_ID, enumFields.pgPCode, enumFields.cgConvTo, enumFields.aDescription, enumFields.fState, enumFields.wWQX_AnalyticalMethodDesc, enumFields.vDescription) - 1
                        For c As Integer = 0 To mvarNumCols - 1
                            With arFieldMapping(c)
                                If (.TableNum <> TableNum Or .FieldNum < MinField Or .FieldNum > MaxField) And .TableNum <> enumTables.tDataTable And .GroupNum <> enumGroups.gIgnore Then success = False : Exit For
                            End With
                        Next

                        If success Then
                            'all field must be OK, so continue here
                            PrintText("Copying import table for direct import...")
                            Application.DoEvents()
                            If TableNum = enumTables.tDataTable Then
                                FileCopy(mvarImportFilename, mvarDataTable)
                            Else
                                FileCopy(mvarImportFilename, PrivateDir & TableName(TableNum - 1) & ".txt")
                            End If
                            Exit Sub
                        End If
                    End If
                Next

                If Not success Then
                    'if it got here, must not match field names
                    PrintText("Unable to perform direct import; invalid field names found.")
                    Exit Sub
                End If

            Else

                If mvarDataSource.DataType = DataSource.clsData.enumDataType.WDM Then
                    'test-wdm is crashing--but not if try to do View first; by testing this seems to avoid error--is probably due to DLL ATC is using
                    Try
                        Process.Start(mvarDataSource.FileName)
                    Catch ex As Exception

                    End Try
                End If

                swData = New IO.StreamWriter(mvarDataTable, AppendToOutput, System.Text.Encoding.ASCII)
                If Not AppendToOutput Then swData.WriteLine("Station_ID~Date_Time~PCode~LEW_Pct~Depth_M~CCode~SCode~LCode~QCode~Agency~RCode~Result~Validated~Create_Update~Track_ID".Replace("~", vbTab))

                PrintText("Will" & IIf(mvarValidateSupport, "", " not") & " validate against Support tables.")

                'now, open output files in case need up add or update and support tables; write field names on first line
                For i = enumTables.tStationTable To enumTables.tMaxTable
                    filSupport(i) = FreeFile()
                    FileOpen(filSupport(i), PrivateDir & TableName(i - 1) & ".txt", IIf(AppendToOutput, OpenMode.Append, OpenMode.Output), OpenAccess.Write, OpenShare.LockWrite)
                    If Not AppendToOutput Then
                        ar = Split(ValidHeading(i), ";")
                        For j = LBound(ar) To UBound(ar)
                            Print(filSupport(i), IIf(j = LBound(ar), "", vbTab) & ar(j))
                        Next j
                        PrintLine(filSupport(i))
                    End If
                Next i

                t1 = VB.Timer()

                If Not MapColumnsToFields(False) Then mvarDataSource.Close() : Exit Sub

                'skip all header lines if standard format (if USGS, skips line after header which contains column formats)
                For i = 1 To Choose(mvarFormat + 1, 5, 3, 3, 3, 0, 1)
                    mvarDataSource.Read()
                Next i

                End If

                With ProgressForm
                    System.Windows.Forms.Application.DoEvents()

                    ReDim arImportData(mvarNumCols - 1)

                    .StartTime = VB.Timer() - 0.01

                    Dim MinTable, MaxTable As Integer

                    If mvarSkipPreview Then 'import all tables in the dataset
                        MinTable = 0
                        MaxTable = Max(0, mvarDataSource.TableNames.Count - 1)
                    Else
                        If mvarDataSource.ActiveTable Is Nothing Then
                            MinTable = 0
                            MaxTable = 0
                        Else
                            MinTable = mvarDataSource.TableNames.IndexOf(RemoveBrackets(mvarDataSource.ActiveTable))
                            MaxTable = MinTable
                        End If
                    End If

                    For tbl As Integer = MinTable To MaxTable
                        If mvarDataSource.TableNames.Count > 0 Then
                            'only import selected WDM scenarios
                            If mvarImportFilename.EndsWith(".wdm", StringComparison.OrdinalIgnoreCase) AndAlso mvarWDMScenario <> "" AndAlso mvarDataSource.TableNames(tbl).ToUpper.Contains(mvarWDMScenario.ToUpper) Then Continue For
                            mvarDataSource.ActiveTable = mvarDataSource.TableNames(tbl)
                        End If

                        Do While mvarDataSource.Read
                            RecNum += 1
                            For i = 0 To Min(mvarNumCols, mvarDataSource.NumItems) - 1
                                arImportData(i) = mvarDataSource.Items(i)
                            Next
                            If ImportRecord(arImportData, swData) Then
                                For skip As Integer = 1 To IIf(mvarIncrementType = clsWRDBImport.enumIncrement.incrRow, mvarIncrement, 1) - 1
                                    If Not mvarDataSource.Read() Then Exit Do
                                Next

                                If mvarSkipPreview And MaxTable <> 0 Then
                                    mvarImportProgress = tbl * 100.0 / MaxTable
                                Else
                                    mvarImportProgress = mvarDataSource.PercentComplete
                                End If

                                If VB.Timer() - LastTimer > 1 Then
                                    System.Windows.Forms.Application.DoEvents()
                                    .UpdateProgress(mvarImportProgress)
                                    LastTimer = VB.Timer()
                                    If .ImportCancelled Then GoTo ImportComplete
                                End If
                            Else
                                Exit For
                            End If
                        Loop
                        'in case adding new in header
                        CheckSupportTable(enumTables.tStationTable, StationID)
                        CheckSupportTable(enumTables.tPCodeTable, PCode)
                        CheckSupportTable(enumTables.tCCodeTable, CCode)
                    Next
                End With

ImportComplete:

                mvarDataSource.Dispose()

                PrintText(String.Format("There were {0} warnings.\nConversion was successful.\n{1} data records were processed.\n{2} support records were added.", IIf(NumWarnings = 0, "NO", NumWarnings), mvarNumRecordsImported, NumSupportRecordsAdded))
                PrintText(String.Format("Elapsed time: {0:0.0} seconds ({1:0.0} records per second).", VB.Timer() - StartTimer, mvarNumRecordsImported / (VB.Timer() - StartTimer)))
                PrintText()

        Catch ex As Exception
            ErrorMsg(, ex)
        Finally
            If swData IsNot Nothing Then
                swData.Close()
                swData.Dispose()
                swData = Nothing
            End If

            For i As Integer = enumTables.tStationTable To enumTables.tMaxTable
                FileClose(filSupport(i))
            Next i

        End Try
    End Sub

#End Region

#Region "Private Methods"

    Private Function ImportRecord(ByRef arImportData() As String, ByRef swData As IO.StreamWriter) As Boolean
        Try
            Dim s As String
            Dim FieldNum As enumFields
            Dim hr, dy, mo, yr, mn As Short
            Dim Mult As Double
            Dim ThisStationID As String = ""
            Dim ThisAgency As String = ""
            Dim ThisRCode As String
            Dim ThisResult As Double
            Dim ThisTime As Date = DateTime.MinValue
            Dim ThisDate As Date = DateTime.MinValue
            Dim ThisDateTime As Date = DateTime.MinValue
            Dim jday As Date = DateTime.MinValue
            Dim ResultList(250) As ResultType 'collect all result fields on this record
            Dim NumResults As Short
            Dim DataFieldFound(enumFields.dTrack_ID) As Boolean
            Dim DateError, TimeError As Boolean

            'take contents of current import record (arImportData) and using mapping information (arFieldMapping) and
            'put data into data and support table image array (arExportData)

            'create a string representing the current line being imported (used for error messages)
            Dim RecordString As New System.Text.StringBuilder
            For i As Integer = 0 To mvarNumCols - 1
                If i > 0 Then RecordString.Append(",")
                RecordString.Append(arImportData(i))
            Next i

            'see if first column has a keyword designating new value for a field in the data file
            'in the past, could only indicate station_id, now can do any field
            FieldNum = ListItem(arImportData(0), DataFieldList)
            If CDbl(FieldNum) <> 0 Then
                If FieldNum = enumFields.dStation_ID Then mvarStationID = arImportData(0)
                If FieldNum = enumFields.dPCode Then mvarPCode = arImportData(0)
                If FieldNum = enumFields.dCCode Then mvarCCode = arImportData(0)
            End If
            FieldNum = ListItem(arImportData(0), DateFieldList)
            If CDbl(FieldNum) <> 0 Then
                If FieldNum = enumFields.tMonth Then mvarMonth = arImportData(0)
                If FieldNum = enumFields.tDay Then mvarDay = arImportData(0)
                If FieldNum = enumFields.tYear Then mvarYear = arImportData(0)
                If FieldNum = enumFields.tTime Then mvarTime = arImportData(0)
            End If

            'set defaults from header (or as updated above)

            Erase arExportData
            ReDim arExportData(enumTables.tMaxTable, 30)

            mo = mvarMonth
            dy = mvarDay
            yr = mvarYear

            If Not CheckTime(mvarTime, ThisTime) Then TimeError = True
            Mult = mvarMultiplier
            Dim mvarFalse As String = "0"
            If mvarCCode Is Nothing Then mvarCCode = ""

            Dim arDefaults As String() = {"", mvarStationID, "1/1/2000", mvarPCode, mvarLEW, mvarDepth, mvarCCode, "", "", "", "", "", "", mvarFalse, Now, ""}
            For i As Integer = enumFields.dStation_ID To enumFields.dTrack_ID
                arExportData(enumTables.tDataTable, i) = arDefaults(i)
            Next i

            If Not String.IsNullOrEmpty(mvarStationGrp) Then arExportData(enumTables.tStationGroupTable, enumFields.sgStationGroup) = mvarStationGrp
            If Not String.IsNullOrEmpty(mvarPCodeGrp) Then arExportData(enumTables.tPCodeGroupTable, enumFields.pgPCodeGroup) = mvarPCodeGrp
            If Not String.IsNullOrEmpty(mvarAgency) Then arExportData(enumTables.tDataTable, enumFields.dAgency) = mvarAgency

            For c As Integer = 0 To mvarNumCols - 1
                With arFieldMapping(c)

                    Select Case .GroupNum
                        Case enumGroups.gPCodeList 'column heading contains a pcode, cell is value
                            AddResult(ResultList, NumResults, arImportData(c), mvarColumns(c), mvarCCodes(c), mvarMultipliers(c), 0, 0, 0)

                        Case enumGroups.gStationList 'column heading contains a station ID, cell is value
                            AddResult(ResultList, NumResults, arImportData(c), mvarPCode, mvarCCodes(c), mvarMultipliers(c), 0, 0, 0)
                            arExportData(enumTables.tDataTable, enumFields.dStation_ID) = mvarColumns(c)

                        Case enumGroups.gIgnore
                            'do nothing

                        Case enumGroups.gNotApplicable 'no group is involved, is direct field name

                            If .TableNum = enumTables.tDataTable Then

                                Dim dt As Date

                                If .FieldNum = enumFields.dDate_Time AndAlso Date.TryParse(arImportData(c), dt) Then
                                    arExportData(.TableNum, .FieldNum) = arImportData(c) 'put directly into the table without conversion
                                    mo = Month(dt)
                                    dy = VB.Day(dt)
                                    yr = Year(dt)
                                    hr = Hour(dt)
                                    mn = Minute(dt)

                                ElseIf .FieldNum = enumFields.dResult Then
                                    AddResult(ResultList, NumResults, arImportData(c), "", mvarCCodes(c), mvarMultipliers(c), 0, 0, 0)

                                Else
                                    arExportData(.TableNum, .FieldNum) = arImportData(c) 'put directly into the table without conversion
                                End If

                            Else
                                arExportData(.TableNum, .FieldNum) = arImportData(c) 'put directly into the table without conversion
                            End If

                        Case enumGroups.gDateFields

                            If .ItemNum = enumFields.tDate_Time Then
                                'If IsDate(arImportData(i)) Then
                                If Not Date.TryParse(arImportData(c), ThisDateTime) Then
                                    '    ThisDateTime = CDate(arImportData(i))
                                    'Else
                                    FatalError("DATE_TIME entries must have standard format--advanced parsing not available for " & arImportData(c))
                                    Return False
                                End If

                            ElseIf .ItemNum = enumFields.tDate_Time_Comb Then
                                If Not CheckDateTime(arImportData(c), ThisDate) Then DateError = True

                            ElseIf .ItemNum = enumFields.tDate Then
                                If Not CheckDate(arImportData(c), ThisDate) Then DateError = True

                            ElseIf ItemIn(.ItemNum, enumFields.tTime, enumFields.tTime_DST) Then
                                If Not CheckTime(arImportData(c), ThisTime) Then TimeError = True

                            ElseIf .ItemNum = enumFields.tJulian Then
                                If IsNumeric(arImportData(c)) Then
                                    ThisDate = DateAdd(Microsoft.VisualBasic.DateInterval.Day, Val(arImportData(c)) - 1, MyCDate("1/1/" & mvarYear))
                                Else
                                    FatalError("Unable to interpret date value: " & arImportData(c))
                                    Return False
                                End If

                            ElseIf .ItemNum = enumFields.tDate_YMD Then
                                If Len(arImportData(c)) <> 8 Then
                                    FatalError("DATE_YMD entries must have standard format of YYYYMMDD--advanced parsing not available for " & arImportData(c))
                                    Return False
                                End If
                                ThisDate = MyCDate(Mid(arImportData(c), 5, 2) & "/" & Right(arImportData(c), 2) & "/" & Left(arImportData(c), 4))

                            ElseIf .ItemNum = enumFields.tYear Then
                                yr = Val(arImportData(c))
                                If yr < 100 Then
                                    yr = yr + 2000
                                    If yr > Year(Today) Then yr = yr - 100
                                End If

                            ElseIf .ItemNum = enumFields.tMonth Then
                                mo = Val(arImportData(c))

                            ElseIf .ItemNum = enumFields.tDay Then
                                dy = Val(arImportData(c))

                            ElseIf .ItemNum = enumFields.tHour Then
                                hr = Val(arImportData(c))

                            ElseIf .ItemNum = enumFields.tMinute Then
                                mn = Val(arImportData(c))

                            End If

                        Case enumGroups.gDayFields 'column is day in month, cell is result
                            AddResult(ResultList, NumResults, arImportData(c), mvarPCode, mvarCCodes(c), mvarMultipliers(c), 0, .ItemNum, 0)

                        Case enumGroups.gHourFields 'column is hour in day, cell is result
                            AddResult(ResultList, NumResults, arImportData(c), mvarPCode, mvarCCodes(c), mvarMultipliers(c), 0, 0, .ItemNum)

                        Case enumGroups.gMonthFields 'column is month in year, cell is result
                            AddResult(ResultList, NumResults, arImportData(c), mvarPCode, mvarCCodes(c), mvarMultipliers(c), .ItemNum, 0, 0)

                        Case enumGroups.gStoretFields

                            If .ItemNum = enumFields.oPCode_Storet Then
                                arExportData(enumTables.tDataTable, enumFields.dPCode) = Right("00000" & arImportData(c), 5)

                            ElseIf .ItemNum = enumFields.oRCode_Storet Then
                                s = Left(arImportData(c), 1)
                                Select Case s
                                    Case "K" : s = "<"
                                    Case "@" : s = ""
                                    Case "L" : s = ">"
                                    Case "P" : s = "T"
                                    Case "T" : s = "N"
                                    Case "M" : s = "<"
                                    Case "<" : s = "<"
                                    Case ">" : s = ">"
                                    Case Else
                                        s = ""
                                End Select
                                arExportData(enumTables.tDataTable, enumFields.dRCode) = s

                            ElseIf .ItemNum = enumFields.oStation_ID_Storet Then
                                ThisStationID = arImportData(c).ToUpper
                                If ThisAgency <> "" Then 'agency column already found--validate
                                    arExportData(enumTables.tDataTable, enumFields.dStation_ID) = Trim(ThisStationID) & "+" & ThisAgency
                                End If

                            ElseIf .ItemNum = enumFields.oAgency_Storet Then
                                ThisAgency = arImportData(c).ToUpper
                                If ThisStationID <> "" Then 'stationid column already found--validate
                                    arExportData(enumTables.tDataTable, enumFields.dStation_ID) = Trim(ThisStationID) & "+" & ThisAgency
                                End If

                            ElseIf .ItemNum = enumFields.oUnits_Storet Then
                                arExportData(enumTables.tPCodeTable, enumFields.pUnits) = Choose(ListItem(arImportData(c).ToUpper, "A;B;C;D;E;F;G;H;I;J;K;L;M;N;O;P;Q;R;S;T;U;V;W;X;Y;Z;; "), "mg/L", "ug/L", "mg/kg,ug/g", "ug/kg", "#/100ml", "#/ml", "#/L", "deg C", "distance", "deg F", "cfs", "gpm,gpd,mgd", "pc/L", "MPN", "percent", "no units", "time", "mmHg", "pH", "pc/gm", "#/sqft", "lb/day,lb/yr", "lb/effort", "production", "wt meas.", "none", "none", "none")
                            End If

                        Case enumGroups.gConvertFields
                            'Dim newValue As String = ""
                            'If dctConvert(.ItemNum).TryGetValue(arImportData(i), newValue) Then
                            '    arExportData(.TableNum, .FieldNum) = newValue
                            'Else
                            '    arExportData(.TableNum, .FieldNum) = "?" & arImportData(i)
                            'End If

                        Case Else

                            System.Diagnostics.Debug.Assert(False, "")

                    End Select

                    'check import filter if applicable (can be used even for columns that were Ignored
                    If Filters(c) <> "" Then
                        If Not (arImportData(c).ToUpper Like Filters(c).ToUpper) Then
                            Static AlreadyWarned As Boolean = False
                            If Not AlreadyWarned Then
                                AlreadyWarned = True
                                PrintText("One or more import filters were applied.")
                            End If
                            Return True
                        End If
                    End If

                    If .GroupNum <> enumGroups.gIgnore Then  'apply conversion group if applicable
                        ConvertGroup.ConvertValue(ConversionGroups(c), arExportData(.TableNum, .FieldNum))
                    End If

                End With

            Next c

            'now all the input columns have been processed; do some post-processing...

            'if result found and all necessary fields found, do more checking and then write data...
            Dim RCodeForRecord As String = arExportData(enumTables.tDataTable, enumFields.dRCode)

            If NumResults > 0 Then
                For i As Integer = 0 To NumResults - 1
                    'ThisDate = Date.MinValue 'added to handle day/month imports with non-blank values in invalid cells
                    System.Array.Clear(DataFieldFound, 0, DataFieldFound.Length)
                    With ResultList(i)
                        ThisRCode = ""
                        If CheckResult(.Result, .Mult, ThisResult, ThisRCode) Then
                            If ThisRCode = "" Then ThisRCode = RCodeForRecord

                            'check date & time (month, day, or hour could vary by column too)

                            If ._Month <> 0 Then mo = ._Month
                            If ._Day <> 0 Then dy = ._Day
                            If ._Hour <> 0 Then hr = ._Hour

                            'date/time may be stored either in ThisDate, ThisTime, or mo,dy,yr,hr,mn

                            If yr <> 0 And dy <> 0 And mo <> 0 Then
                                Try
                                    ThisDate = New Date(yr, mo, dy)
                                Catch ex As Exception
                                    DateError = True
                                End Try
                            End If

                            'Dim dt As String = String.Format("{0}/{1}/{2}", mo, dy, yr)
                            'If IsDate(dt) Then ThisDate = CDate(dt) Else DateError = True

                            If ThisDate = MyCDate("1/1/1900") And DateError Then
                                s = "Warning:  date is invalid on line " & RecNum & ": "
                                For j As Integer = 0 To mvarNumCols - 1
                                    s = s & IIf(j = 0, "", ",") & arImportData(j)
                                Next j
                                PrintWarning(s & "...data ignored", NumWarnings)
                                Continue For
                            End If

                            If ThisDate.Year < 1800 Or ThisDate.Year > 2100 Then
                                s = "Warning:  date is suspect on line " & RecNum & ": "
                                For j As Integer = 0 To mvarNumCols - 1
                                    s = s & IIf(j = 0, "", ",") & arImportData(j)
                                Next j
                                PrintWarning(s & "...examine data carefully", NumWarnings)
                            End If

                            If hr = 24 Then
                                ThisDateTime = ThisDate.AddDays(1)
                            Else
                                If ThisTime = DateTime.MinValue Then
                                    Try
                                        ThisTime = CDate(hr & ":" & mn)
                                    Catch ex As Exception
                                        PrintWarning("Warning:  time is invalid on line " & RecNum & ": " & RecordString.ToString & "...reset to 00:00", NumWarnings)
                                        ThisTime = CDate("00:00")
                                    End Try
                                End If
                                ThisDateTime = ThisDate.AddHours(ThisTime.Subtract(New Date(0)).TotalHours)
                            End If

                            If mvarDST Then ThisDateTime = DateAdd(Microsoft.VisualBasic.DateInterval.Hour, -1, ThisDateTime) 'subtract one hour to convert to standard time

                            If Second(ThisDateTime) > 30 Then ThisDateTime = DateAdd(Microsoft.VisualBasic.DateInterval.Minute, 1, ThisDateTime) 'round to nearest minute

                            arExportData(enumTables.tDataTable, enumFields.dDate_Time) = ThisDateTime.ToString(MyDateTimeFormat)

                            arExportData(enumTables.tDataTable, enumFields.dResult) = IIf(ThisResult = Double.MinValue, "", ThisResult)
                            arExportData(enumTables.tDataTable, enumFields.dRCode) = ThisRCode
                            If .CCode <> "" Then arExportData(enumTables.tDataTable, enumFields.dCCode) = .CCode
                            If .PCode <> "" Then arExportData(enumTables.tDataTable, enumFields.dPCode) = .PCode

                            MakeUpperCase(enumTables.tDataTable, enumFields.dStation_ID, enumFields.dPCode, enumFields.dRCode, enumFields.dCCode, enumFields.dAgency)

                            'now i have the entire export record assembled, write to output table or file

                            For j As Integer = enumFields.dStation_ID To enumFields.dTrack_ID
                                If arExportData(enumTables.tDataTable, j) <> "" Then DataFieldFound(j) = True
                            Next j

                            If DataFieldFound(enumFields.dStation_ID) AndAlso DataFieldFound(enumFields.dDate_Time) AndAlso DataFieldFound(enumFields.dPCode) AndAlso (DataFieldFound(enumFields.dRCode) Or DataFieldFound(enumFields.dResult)) Then

                                mvarNumRecordsImported = mvarNumRecordsImported + 1

                                For j As Integer = enumFields.dStation_ID To enumFields.dTrack_ID
                                    'note: at some point the following two lines were disabled for some reason; now (8/8/05) I see I need them
                                    If j = enumFields.dDate_Time Then
                                        swData.Write(String.Format("{0:yyyy-MM-dd HH:mm}", arExportData(enumTables.tDataTable, j)))
                                    ElseIf j = enumFields.dLEW_Pct Or j = enumFields.dDepth_M Or j = enumFields.dTrack_ID Then
                                        Dim v As Double = ParseNumber(arExportData(enumTables.tDataTable, j))
                                        swData.Write(IIf(v = Double.MinValue, "", Val(arExportData(enumTables.tDataTable, j))))
                                    Else
                                        swData.Write(Strings.Left(arExportData(enumTables.tDataTable, j).ToUpper, arFieldWidth(enumTables.tDataTable)(j - 1)).Trim.ToUpper)
                                    End If
                                    swData.Write(IIf(j = enumFields.dTrack_ID, vbCrLf, vbTab))
                                Next j

                                If mvarValidateSupport Then 'compare result with valid range in pcode table
                                    Dim pcode As String = Strings.Left(arExportData(enumTables.tDataTable, enumFields.dPCode), arFieldWidth(enumTables.tDataTable)(enumFields.dPCode)).Trim.ToUpper
                                    If dcResultMinMax.ContainsKey(pcode) Then
                                        Dim min As Double = dcResultMinMax(pcode).Min
                                        Dim max As Double = dcResultMinMax(pcode).Max
                                        Dim val As Double = TestNull(arExportData(enumTables.tDataTable, enumFields.dResult), Double.MinValue)
                                        If val <> Double.MinValue AndAlso min <> 0 AndAlso max <> 0 AndAlso (val < min Or val > max) Then
                                            PrintWarning(NumWarnings, "Warning:  value ({0}) is outside valid range for {1} ({2} to {3}) at line {4}: {5}", arExportData(enumTables.tDataTable, enumFields.dResult), pcode, min, max, RecNum, RecordString)
                                        End If
                                    End If
                                End If
                            End If
                        End If 'checkresult
                    End With

                Next i
            End If

            If mvarValidateSupport Or ImportSupport Then
                'update associated support tables (also if only importing to support table)
                'if key value is blank, nothing happens

                'make sure required fields are not empty
                FillRequiredField(enumTables.tStationTable, enumFields.sStation_Name, "Unknown")
                FillRequiredField(enumTables.tPCodeTable, enumFields.pAnal_Name, "Unknown")
                FillRequiredField(enumTables.tPCodeTable, enumFields.pDet_Limit, 0)
                FillRequiredField(enumTables.tPCodeTable, enumFields.pSig_Figs, 2)
                FillRequiredField(enumTables.tPCodeTable, enumFields.pMin_Limit, "") 'leave limits as NULL entries
                FillRequiredField(enumTables.tPCodeTable, enumFields.pMax_Limit, "")
                FillRequiredField(enumTables.tPCodeTable, enumFields.pMin_Criteria, "")
                FillRequiredField(enumTables.tPCodeTable, enumFields.pMax_Criteria, "")
                FillRequiredField(enumTables.tPCodeTable, enumFields.pFormat_Str, "w.2")
                FillRequiredField(enumTables.tCCodeTable, enumFields.cCCode_Name, "Unknown")
                FillRequiredField(enumTables.tBranchTable, enumFields.bBranch_Name, "Unknown")
                FillRequiredField(enumTables.tQCodeTable, enumFields.qQCode_Name, "Unknown")

                'some station information may have been found in the header (e.g., USGS/BASINS); if so, place it there assuming one station per import file
                FillRequiredField(enumTables.tStationTable, enumFields.sStation_Name, StationName)
                FillRequiredField(enumTables.tStationTable, enumFields.sStation_Type, StationType)
                FillRequiredField(enumTables.tStationTable, enumFields.sAgency, Agency)
                FillRequiredField(enumTables.tStationTable, enumFields.sHUC, HUC)
                FillRequiredField(enumTables.tStationTable, enumFields.sLatitude, Latitude)
                FillRequiredField(enumTables.tStationTable, enumFields.sLongitude, Longitude)
                FillRequiredField(enumTables.tStationTable, enumFields.sDr_Area, DrArea)

                'some pcode information may have been found in the header (e.g., USGS/BASINS); if so, place it there assuming one station per import file
                Dim Anal_Name As String = "Unknown"
                If arExportData(enumTables.tDataTable, enumFields.dPCode) IsNot Nothing AndAlso PCodes.TryGetValue(arExportData(enumTables.tDataTable, enumFields.dPCode), Anal_Name) Then
                    arExportData(enumTables.tPCodeTable, enumFields.pAnal_Name) = Anal_Name
                End If

                'these are hacks--the only non-key field name duplicated in multiple tables!!!
                arExportData(enumTables.tStationTable, enumFields.sAgency) = arExportData(enumTables.tDataTable, enumFields.dAgency)
                arExportData(enumTables.tQCodeTable, enumFields.qDescription) = arExportData(enumTables.tCCodeTable, enumFields.cDescription)
                arExportData(enumTables.tAgencyTable, enumFields.aDescription) = arExportData(enumTables.tCCodeTable, enumFields.cDescription)

                'set certain fields to uppercase
                MakeUpperCase(enumTables.tStationTable, enumFields.sStation_ID, enumFields.sAgency, enumFields.sBasin_Name, enumFields.sBranch_ID, enumFields.sConvert_Station_ID, enumFields.sEcoRegion, enumFields.sPlanRegion, enumFields.sState, enumFields.sStation_Type, enumFields.sWaterBody)
                MakeUpperCase(enumTables.tPCodeTable, enumFields.pPCode, enumFields.pConvert_PCode, enumFields.pWQX_AnalyticalMethodIDContext)
                MakeUpperCase(enumTables.tCCodeTable, enumFields.cCCode, enumFields.cConvert_CCode)
                MakeUpperCase(enumTables.tRCodeTable, enumFields.rRCode, enumFields.rConvert_RCode)
                MakeUpperCase(enumTables.tQCodeTable, enumFields.qQCode)
                MakeUpperCase(enumTables.tBranchTable, enumFields.bBranch_ID, enumFields.bDS_Branch_ID)
                MakeUpperCase(enumTables.tAgencyTable, enumFields.aAgency)
                MakeUpperCase(enumTables.tStationGroupTable, enumFields.sgStation_ID, enumFields.sgStationGroup)
                MakeUpperCase(enumTables.tPCodeGroupTable, enumFields.pgPCode, enumFields.pgPCodeGroup)
                MakeUpperCase(enumTables.tValidationTable, enumFields.vValid_Value)
                MakeUpperCase(enumTables.tWQXMethods, enumFields.wWQX_AnalyticalMethodIDContext)

                CheckSupportTable(enumTables.tStationTable, arExportData(enumTables.tDataTable, enumFields.dStation_ID))
                CheckSupportTable(enumTables.tPCodeTable, arExportData(enumTables.tDataTable, enumFields.dPCode))
                CheckSupportTable(enumTables.tCCodeTable, arExportData(enumTables.tDataTable, enumFields.dCCode))
                CheckSupportTable(enumTables.tRCodeTable, arExportData(enumTables.tDataTable, enumFields.dRCode))
                CheckSupportTable(enumTables.tQCodeTable, arExportData(enumTables.tDataTable, enumFields.dQCode))
                CheckSupportTable(enumTables.tBranchTable, arExportData(enumTables.tStationTable, enumFields.sBranch_ID))

                CheckSupportTable(enumTables.tStationGroupTable, arExportData(enumTables.tStationGroupTable, enumFields.sgStationGroup))
                CheckSupportTable(enumTables.tPCodeGroupTable, arExportData(enumTables.tPCodeGroupTable, enumFields.pgPCodeGroup))
                CheckSupportTable(enumTables.tConvGroupTable, arExportData(enumTables.tConvGroupTable, enumFields.cgConvGroup))

                CheckSupportTable(enumTables.tAgencyTable, arExportData(enumTables.tDataTable, enumFields.dAgency))
                CheckSupportTable(enumTables.tFIPSCodes, arExportData(enumTables.tFIPSCodes, enumFields.fFIPS_Code))
                CheckSupportTable(enumTables.tWQXMethods, arExportData(enumTables.tWQXMethods, enumFields.wWQX_AnalyticalMethodIDContext))
                CheckSupportTable(enumTables.tValidationTable, arExportData(enumTables.tValidationTable, enumFields.vField_Name))

            End If
            Return True
        Catch ex As Exception
            ErrorMsg(, ex)
            Return False
        End Try
    End Function

    Private Sub MakeUpperCase(ByVal Table As enumTables, ByVal ParamArray UpperCaseFields() As enumFields)
        For Each ff As enumFields In UpperCaseFields
            If arExportData(Table, ff) IsNot Nothing Then arExportData(Table, ff) = arExportData(Table, ff).ToUpper
        Next
    End Sub

    Private Sub AddResult(ByRef ResultList() As ResultType, ByRef NumResults As Short, ByVal Result As String, ByVal PCode As String, ByVal CCode As String, ByVal Mult As String, ByVal _Month As Short, ByVal _Day As Short, ByVal _Hour As Short)
        NumResults += 1
        With ResultList(NumResults - 1)
            .Result = Result
            .PCode = PCode
            .CCode = CCode
            .Mult = Mult
            ._Month = _Month
            ._Day = _Day
            ._Hour = _Hour
        End With
    End Sub

    Private Sub FillRequiredField(ByVal TableNum As enumTables, ByVal FieldNum As enumFields, ByVal DefaultValue As String)
        If (arExportData(TableNum, FieldNum) = "" OrElse arExportData(TableNum, FieldNum) = "Unknown") AndAlso DefaultValue <> "" Then arExportData(TableNum, FieldNum) = DefaultValue
    End Sub

    Private Function MapColumnsToFields(Optional ByRef PromptForJulian As Boolean = True) As Boolean
        Dim i As Short
        Dim j As enumGroups
        Dim s As String
        Static JulianYear As Short
        Dim DataFieldFound(enumFields.dTrack_ID) As Boolean

        'search through lists of valid table field names and pcodes for specified column heading
        'and assign correct table and field index. as seen in the declaration:

        'Private Type FieldMappingType
        '  TableNum As Integer 'pointer to type of table to write to (data, stations, etc.)
        '  FieldNum As Integer 'pointer to field in that table (date_time)
        '  GroupNum As Integer 'pointer to special header group that requires additional processing (storet); -1 if not applicable
        '  ItemNum As Integer  'pointer to item in that special group (pcodestoret)
        'End Type

        For i = 0 To mvarNumCols - 1
            mvarColumns(i) = mvarColumns(i).ToUpper
            mvarCCodes(i) = mvarCCodes(i).ToUpper
            mvarMultipliers(i) = mvarMultipliers(i).ToUpper
            Dim minGroup As enumGroups = enumGroups.gDataTable
            'If mvarAssumeSupport Then minGroup = enumGroups.gStationTable
            For j = minGroup To enumGroups.gMaxGroup
                If ListItem(mvarColumns(i), ValidHeading(j)) <> 0 Then

                    With arFieldMapping(i)

                        Select Case j

                            Case enumGroups.gIgnore
                                .TableNum = j
                                .FieldNum = 0
                                .GroupNum = j
                                .ItemNum = 0

                            Case enumGroups.gDataTable, enumGroups.gStationTable To enumGroups.gValidationTable  'valid data file field name
                                .TableNum = j
                                .FieldNum = ListItem(mvarColumns(i), ValidHeading(j))
                                .GroupNum = enumGroups.gNotApplicable
                                .ItemNum = 0

                            Case enumGroups.gDateFields 'parse date
                                .TableNum = enumTables.tDataTable
                                .FieldNum = enumFields.dDate_Time
                                .GroupNum = j
                                .ItemNum = ListItem(mvarColumns(i), ValidHeading(j))
                                If .ItemNum = enumFields.tTime_DST Then mvarDST = True 'set a flag to add an hour to input times to convert to EST

                            Case enumGroups.gDayFields To enumGroups.gMonthFields 'day 1, Jan, etc.
                                .TableNum = enumTables.tDataTable
                                .FieldNum = enumFields.dDate_Time
                                .GroupNum = j
                                .ItemNum = ListItem(mvarColumns(i), ValidHeading(j))

                            Case enumGroups.gStoretFields 'do Storet conversions
                                .TableNum = enumTables.tDataTable 'todo: differentiate btwn storet conversions for other support tables
                                .FieldNum = 0 'depends...
                                .GroupNum = j
                                .ItemNum = ListItem(mvarColumns(i), ValidHeading(j))

                            Case enumGroups.gConvertFields 'do lookup conversions
                                .TableNum = enumTables.tDataTable
                                .GroupNum = j
                                .ItemNum = ListItem(mvarColumns(i), ValidHeading(j))
                                .FieldNum = Choose(.ItemNum, enumFields.dPCode, enumFields.dRCode, enumFields.dStation_ID)

                            Case enumGroups.gPCodeList
                                .TableNum = enumTables.tDataTable
                                .FieldNum = enumFields.dResult
                                .GroupNum = j
                                .ItemNum = ListItem(mvarColumns(i), ValidHeading(j))

                            Case enumGroups.gStationList
                                .TableNum = enumTables.tDataTable
                                .FieldNum = enumFields.dResult
                                .GroupNum = j
                                .ItemNum = ListItem(mvarColumns(i), ValidHeading(j))

                        End Select

                    End With
                    Exit For
                End If
            Next j
            If j > enumGroups.gIgnore Then
                MsgBox("Unable to map name in cell to standard item: " & mvarColumns(i), MsgBoxStyle.Critical, "Error")
                Return False
            End If
        Next i

        'now go through all columns and flag special ones...
        For i = 0 To mvarNumCols - 1
            With arFieldMapping(i)

                If .TableNum = enumTables.tDataTable Then
                    If .FieldNum = enumFields.dStation_ID Then
                        DataFieldFound(enumFields.dStation_ID) = True
                    ElseIf .FieldNum = enumFields.dPCode Or .GroupNum = enumGroups.gPCodeList Then
                        DataFieldFound(enumFields.dPCode) = True
                        DataFieldFound(enumFields.dResult) = True
                    ElseIf .FieldNum = enumFields.dDate_Time Or .GroupNum = enumGroups.gDateFields Then
                        DataFieldFound(enumFields.dDate_Time) = True
                    ElseIf .FieldNum = enumFields.dResult Then
                        DataFieldFound(enumFields.dResult) = True
                    End If
                End If

                Select Case .GroupNum
                    Case enumGroups.gStationList
                        DataFieldFound(enumFields.dStation_ID) = True
                        DataFieldFound(enumFields.dResult) = True
                    Case enumGroups.gPCodeList
                        DataFieldFound(enumFields.dPCode) = True
                        DataFieldFound(enumFields.dResult) = True
                    Case enumGroups.gStoretFields
                        Select Case .ItemNum
                            Case enumFields.oPCode_Storet
                                DataFieldFound(enumFields.dPCode) = True
                            Case enumFields.oRCode_Storet
                                DataFieldFound(enumFields.dRCode) = True
                            Case enumFields.oStation_ID_Storet
                                DataFieldFound(enumFields.dStation_ID) = True
                        End Select
                    Case enumGroups.gDateFields
                        Select Case .ItemNum
                            Case enumFields.tTime, enumFields.tHour
                                PrintText("Using standard time...") 'time or hour field
                            Case enumFields.tTime_DST
                                mvarDST = True : PrintText("Converting from daylight saving time to standard time...") 'dst field
                            Case enumFields.tJulian
                                If PromptForJulian Then
                                    Beep()
                                    PrintText()
                                    PrintText("Column heading JULIAN found used for importing")
                                    PrintText("Corps of Engineers monitoring data.  This")
                                    PrintText("represents the number of days since January 1")
                                    PrintText("of a specified year.  To correctly compute")
                                    PrintText("the date, you must now enter the year the data")
                                    PrintText("were collected in the form YY or YYYY.")
                                    PrintText("For example, for 1994 data just enter 94;")
                                    PrintText("for year 2003 data enter 2003.")
                                    PrintText()
                                    Do
                                        s = InputBox("Column heading JULIAN found used for importing Corps of Engineers monitoring data. " & "Enter the year the data were collected in the form YY or YYYY.", "WRDB Import needs data...")
                                        If s = "" Then Return False
                                        JulianYear = Val(s)
                                    Loop Until JulianYear > 0
                                End If
                        End Select
                    Case enumGroups.gDayFields, enumGroups.gHourFields, enumGroups.gMonthFields
                        DataFieldFound(enumFields.dDate_Time) = True
                        DataFieldFound(enumFields.dResult) = True
                        'Case enumGroups.gConvertFields
                        '    If Not ReadConvertFile(.ItemNum, True) Then Return False
                End Select
            End With
        Next i

        If mvarAssumeSupport Then
            mvarValidateSupport = True
            ImportSupport = True
        Else
            If mvarStationID <> "" Then DataFieldFound(enumFields.dStation_ID) = True
            If mvarYear <> 0 Then DataFieldFound(enumFields.dDate_Time) = True
            If mvarPCode <> "" Then DataFieldFound(enumFields.dPCode) = True
            Dim MissingFields As String = ""
            For f As Integer = 1 To 4
                If Not DataFieldFound(Choose(f, enumFields.dStation_ID, enumFields.dDate_Time, enumFields.dPCode, enumFields.dResult)) Then
                    MissingFields &= IIf(MissingFields = "", "", ", ") & Choose(f, "Station_ID", "Date_Time", "PCode", "Result")
                End If
            Next
            If Not String.IsNullOrEmpty(MissingFields) Then
                If WarningMsgFmt("The following required data fields were missing so data cannot be imported into a WRDB data table:\n\n{0}\n\nThis may be because you have incorrect or incomplete column or import headings. If this is the case, click 'Cancel' to cancel the import operation so you can try again.\n\nAnother explanation is that you wish to just import Support table information from this file or table. If that is the case, click 'OK' to continue and all applicable Support tables will be updated using whatever information has been supplied.\n\nContinue with import?", MissingFields) = DialogResult.OK Then
                    mvarValidateSupport = True
                    ImportSupport = True
                Else
                    Return False
                End If
            End If
        End If

        Return True

    End Function

    '''' <summary>
    '''' Read the specified "convert" file which has pairs of comma-separated original and convert-to values into memory
    '''' </summary>
    '''' <param name="Index">Which conversion file to read</param>
    '''' <param name="ErrorIfNotFound">Stop reading and issue error message in file is not found</param>
    '''' <returns>True if successful</returns>
    'Private Function ReadConvertFile(ByVal Index As enumConvert, Optional ByRef ErrorIfNotFound As Boolean = True) As Boolean
    '    Dim sr As IO.StreamReader = Nothing
    '    Try

    '        Dim filename As String = ConvertDir & "\" & Choose(CInt(Index), "PCode", "RCode", "Station") & "Convert.csv"

    '        If My.Computer.FileSystem.FileExists(filename) Then
    '            dctConvert(Index) = New Generic.Dictionary(Of String, String)
    '            sr = New IO.StreamReader(filename)
    '            Do
    '                Dim l As String = sr.ReadLine
    '                If l Is Nothing Then Exit Do
    '                Dim ar() As String = l.Split(",")
    '                If ar.Length <> 2 Then
    '                    WarningMsg(String.Format("Error reading conversion file {0}. The file must contain two columns of data separated by commas. The last line read was:" & vbCr & vbCr & l, filename))
    '                    Return False
    '                End If
    '                dctConvert(Index).Add(ar(0), ar(1))
    '            Loop
    '            If dctConvert(Index).Count = 0 And ErrorIfNotFound Then
    '                WarningMsg("No conversion values were found in " & filename)
    '            Else
    '                PrintText("Override file is being used: " & filename)
    '            End If
    '            Return True
    '        Else
    '            If ErrorIfNotFound Then
    '                WarningMsg(String.Format("The conversion file called {0} was not found. This file contains two columns of data separated by commas and must be located in the indicated directory. The first column is the value you wish to convert FROM and the second is the one you wish to convert TO. Any values in your import file not found in the first column of this file will be set to '?xxxxx' where xxxxx is the original value.", filename))
    '            Else
    '                PrintText(filename & " override file not found; no overrides will be used.")
    '            End If
    '            Return False
    '        End If

    '    Catch ex As Exception
    '        ErrorMsg(, ex)
    '    Finally
    '        If sr IsNot Nothing Then
    '            sr.Close()
    '            sr.Dispose()
    '            sr = Nothing
    '        End If
    '    End Try
    'End Function

    ''' <summary>
    ''' Add support record based on existence of keyfield in list of records
    ''' </summary>
    ''' <param name="TableNum">Selected support table to check</param>
    ''' <param name="IndexValue">The keyfield value to test</param>
    Private Sub CheckSupportTable(ByVal TableNum As enumTables, ByVal IndexValue As String)
        Dim i As Short
        Dim AddNew As Boolean
        Dim ar As Object
        Static NumWarnings As Integer

        IndexValue = Trim(Left(IndexValue, arFieldWidth(TableNum)(0))).ToUpper

        If Trim(IndexValue) = "" Then Exit Sub

        'check for existence of index in list (it is alot faster to do string search than table search)

        'If ImportSupport Then 'just importing support table, so don't check for duplicates, takes too long
        '    AddNew = True
        'Else
        AddNew = Not FieldListContains(TableNum, IndexValue) 'not found, add it
        'End If

        If AddNew Then

            NumSupportRecordsAdded += 1

            'If Not ImportSupport Then ValidList(TableNum).Append(";" & IndexValue)
            If ValidList(TableNum) Is Nothing Then ValidList(TableNum) = New System.Text.StringBuilder
            ValidList(TableNum).Append(";" & IndexValue)

            If TableNum = enumTables.tPCodeTable Then 'must add min/max for this new item too
                Dim MinMax As structMinMax
                MinMax.Min = -9999999
                MinMax.Max = 9999999
                dcResultMinMax.Add(IndexValue, MinMax)
            End If

            If Not AssumeSupport Then
                NumWarnings = NumWarnings + 1
                If NumWarnings = 500 Then PrintText("The maximum of 500 messages have been displayed...")
                If NumWarnings < 500 Then
                    PrintText("Adding to " & Choose(TableNum - 6, "Stations", "PCode", "CCode", "RCode", "QCode", "Branches", "Station Group", "PCode Group") & " Support table: '" & IndexValue & "'")
                End If
            End If

            ar = Split(ValidHeading(TableNum), ";")

            'print the index value from the data record, then the other fields from the support table
            'this works for all support tables except the 'group' tables, in which the index value is the group name
            'and the only other field is the station_ID (or pcode) value

            Print(filSupport(TableNum), IndexValue)

            If TableNum = enumTables.tStationGroupTable Then
                Print(filSupport(TableNum), vbTab & arExportData(enumTables.tDataTable, enumFields.dStation_ID))

            ElseIf TableNum = enumTables.tPCodeGroupTable Then
                Print(filSupport(TableNum), vbTab & arExportData(enumTables.tDataTable, enumFields.dPCode))

            Else
                For i = 2 To UBound(ar) - LBound(ar) + 1
                    Print(filSupport(TableNum), vbTab & Left(arExportData(TableNum, i), arFieldWidth(TableNum)(i - 1)).Trim)
                Next
            End If
            PrintLine(filSupport(TableNum))
        End If

    End Sub

    ''' <summary>
    ''' Check validity of imported result value (possibly containing and RCode or other text value) and return parsed value
    ''' </summary>
    ''' <param name="FieldValue">Text string for result as imported</param>
    ''' <param name="Multiplier">Desired multiplier (or conversion string like C2F)</param>
    ''' <param name="Result">Numeric result</param>
    ''' <param name="RCode">Valid RCode</param>
    ''' <returns>True if successfully parsed</returns>
    Private Function CheckResult(ByVal FieldValue As String, ByVal Multiplier As String, ByRef Result As Double, ByRef RCode As String) As Boolean
        Dim d2, d, e As Short
        Dim ResultString As String
        Static NumWarnings As Integer
        Dim Mult As Double
        Dim GlobalMultiplier As String

        ResultString = TestNull(FieldValue, "").Trim.ToUpper

        'strip trailing cr that may be present for some reason
        ResultString = Replace(ResultString, vbCr, "")

        If ResultString = "" Then Return False

        Result = ParseNumber(ResultString)
        RCode = ""

        If Result = Double.MinValue Then 'non-numeric format: try to interpret string
            'replace common non-detect strings
            ResultString = ResultString.ToUpper.Replace("ND", "<").Replace("BDL", "<").Replace("NOT DETECTED", "<").Replace("BELOW DET LIM", "<")

            'replace common too numerous to count strings
            ResultString = ResultString.ToUpper.Replace("TNTC", ">")

            'look for NA and N/A, etc.
            If ";NA;N/A;NOT ANALYZED;NO BOTTLE;M;DRY;".Contains(";" & ResultString & ";") Then Return False

            'look for numerics used by STORET
            If ParseNumber(ResultString) = -999999 Or ParseNumber(ResultString) = -9999 Or ParseNumber(ResultString) = 99999 Then
                NumStoretIgnored = NumStoretIgnored + 1
                Return False
            End If

            'now override first character only and replace with first character of new rcode
            If mvarRCodeConvGroup <> "(None)" Then
                Dim r As String = ResultString.Substring(0, 1)
                ConvertGroup.ConvertValue(RCodeConvGroup, r)
                Mid(ResultString, 1, 1) = r
            End If

            If InStr("+-.", Left(ResultString, 1)) = 0 Then 'set rcode to first character and strip it off (but don't let rcode be numeric in case have 23.45XXX)
                RCode = Left(ResultString, 1)
                If IsNumeric(RCode) Then RCode = ""
                If Len(ResultString) > 1 Then ResultString = Mid(ResultString, 2) Else ResultString = ""
            End If

            'provide warning of suspect data (could have invalid number or string)

            If ParseNumber(ResultString) = Double.MinValue And RCode = "" Then
                NumWarnings += 1
                PrintWarning("Invalid entry for observed value on record " & RecNum & ":  " & ResultString, NumWarnings)
                Return False
            End If
            Result = ParseNumber(ResultString)
        End If

        'now, apply multiplier

        If Multiplier = "C2F" Then 'convert celsius to fahrenheit
            Result = Result * 9 / 5 + 32
        ElseIf Multiplier = "F2C" Then  'convert fahrenheit to celsius
            Result = (Result - 32) * 5 / 9
        ElseIf Multiplier = "COEF2C" Then  'convert fahrenheit*10 to celsius
            Result = (Result / 10 - 32) * 5 / 9
        Else
            'Mult = Val(Multiplier)
            Mult = ParseNumber(Multiplier)
            If Mult = Double.MinValue Then Mult = 1 'invalid entries will become 1.0
            Result *= Mult
        End If

        'multiplier may have caused lots of significant digits;
        'figure out how many decimal places to round to based on number of decimals in input value (or multiplier)

        'if global multiplier is set, multiply it by the individuals
        If mvarMultiplier = 0 Then mvarMultiplier = 1
        GlobalMultiplier = mvarMultiplier
        Result *= mvarMultiplier

        If mvarMultiplier <> 1 Or Mult <> 1 Or ListItem(Multiplier, "C2F;F2C;COEF2C") <> 0 Then
            d = InStr(ResultString, ".") 'if not found, assumes no decimals
            If d <> 0 Then d = Len(ResultString) - d 'number of characters to right of decimal
            e = InStr(ResultString, "E") 'see if exponential
            If e <> 0 Then d = d - Val(Mid(ResultString, e + 1))

            'now based on multiplier

            d2 = InStr(Multiplier, ".") 'if not found, assumes no decimals
            If d2 <> 0 Then d2 = Len(Multiplier) - d2 'number of characters to right of decimal
            If d2 > d Then d = d2 'give greater number of decimals (result or multiplier)

            'now based on global multiplier

            d2 = InStr(GlobalMultiplier, ".") 'if not found, assumes no decimals
            If d2 <> 0 Then d2 = Len(GlobalMultiplier) - d2 'number of characters to right of decimal
            If d2 > d Then d = d2 'give greater number of decimals (result or multiplier)

            'if using equation, always give 2 decimals
            If ListItem(Multiplier, "C2F;F2C;COEF2C") <> 0 Then d = 2

            Result = System.Math.Round(Result, d)
        End If

        Return True

    End Function

    ''' <summary>
    ''' Check validity of imported date string and return parsed value
    ''' </summary>
    ''' <param name="Field">Imported date string</param>
    ''' <param name="ThisDate">Parsed date</param>
    ''' <returns>True if successfully parsed date</returns>
    Private Function CheckDate(ByVal Field As String, ByRef ThisDate As Date) As Boolean

        '  If IsDate(Field) Then 'added 8/8/05 to allow reading "February 12, 2005"
        'nothing to do

        If InStr(Field, "/") <> 0 Then
            'nothing to do

        ElseIf InStr(Field, "-") <> 0 Then
            'nothing to do

        ElseIf Field.Length <= 6 Then 'no separators, assume format is either MMDDYY or MDDYY
            Field = Right("0" & Field, 6)
            Field = Left(Field, 2) & "/" & Mid(Field, 3, 2) & "/" & Mid(Field, 5)

        Else  'no separators, assume format is either MMDDYYYY or MDDYYYY
            Field = Right("0" & Field, 8)
            Field = Left(Field, 2) & "/" & Mid(Field, 3, 2) & "/" & Mid(Field, 5)

        End If
        'If Date.TryParse(Field, ThisDate) Then
        ThisDate = ParseDate(Field)
        If ThisDate <> Date.MinValue Then
            'VB will turn 1/1/20 into year 2020; if greater than current year, want it to be 1920
            If Year(ThisDate) > Year(Today) Then ThisDate = DateAdd(Microsoft.VisualBasic.DateInterval.DayOfYear, -100, ThisDate)
            Return True
        Else
            ThisDate = MyCDate("1/1/1900")
            Return False
        End If

    End Function

    ''' <summary>
    ''' Check validity of imported time string and return parsed value
    ''' </summary>
    ''' <param name="Field">Imported time string</param>
    ''' <param name="ThisTime">Parsed time</param>
    ''' <returns>True if successfully parsed time</returns>
    Private Function CheckTime(ByVal Field As String, ByRef ThisTime As Date) As Boolean
        Dim AddDay As Boolean

        If String.IsNullOrEmpty(Field) Or Field = "---" Then
            Field = "00:00"

        ElseIf InStr(Field, ":") <> 0 Then  'assume is properly formatted; let vb figure it out
            'nothing to do

        ElseIf InStr(Field, ".") <> 0 Then  'Quattro may bring in times as decimal days (VB can handle)
            Field = CStr(DateAdd(Microsoft.VisualBasic.DateInterval.Hour, Val(Field) * 24, ThisTime))

        Else
            If Len(Field) <= 6 Then
                Field = Choose(Len(Field), "00:0" & Field, "00:" & Field, Left(Field, 1) & ":" & Mid(Field, 2), Left(Field, 2) & ":" & Mid(Field, 3), Left(Field, 1) & ":" & Mid(Field, 2, 2) & ":" & Mid(Field, 4), Left(Field, 2) & ":" & Mid(Field, 3, 2) & ":" & Mid(Field, 5))
            End If
        End If

        If Val(Field) = 25 Then Field = "00:00" 'special time mean "no time"

        If Val(Field) = 24 Then Field = "00:00" : AddDay = True

        If Date.TryParseExact(Field, "H:mm", My.Application.Culture, System.Globalization.DateTimeStyles.NoCurrentDateDefault, ThisTime) Then
            If AddDay Then ThisTime = DateAdd(Microsoft.VisualBasic.DateInterval.Day, 1, ThisTime)
            Return True
        ElseIf IsDate(Field) Then
            ThisTime = CDate(Field)
            If AddDay Then ThisTime = DateAdd(Microsoft.VisualBasic.DateInterval.Day, 1, ThisTime)
            Return True
        Else
            ThisTime = New Date(0)
            Return False
        End If

        Return False
    End Function

    ''' <summary>
    ''' Check validity of imported data/time string and return parsed value
    ''' </summary>
    ''' <param name="Field">Imported date-time string (format is YYYYMMDDHHMMSS as found in RDB files)</param>
    ''' <param name="ThisDateTime">Parsed data-time</param>
    ''' <returns>True if successfully parsed date-time</returns>
    Private Function CheckDateTime(ByVal Field As String, ByRef ThisDateTime As Date) As Boolean
        ThisDateTime = MyCDate("1/1/1900")
        Return Date.TryParseExact(Field, "yyyyMMddHHmmss", Nothing, Globalization.DateTimeStyles.None, ThisDateTime)

        Try
            ThisDateTime = New Date(Field.Substring(0, 4), Field.Substring(4, 2), Field.Substring(6, 2), Field.Substring(8, 2), Field.Substring(10, 2), Field.Substring(12, 2))
        Catch ex As Exception
            Return False
        End Try
    End Function

#End Region

    ''' <summary>
    ''' Setup standard fieldnames and arrays
    ''' </summary>
    Public Sub New(ByRef _PrintDoc As Drawing.Printing.PrintDocument, ByVal _WRDBProject As WRDB.Project.clsProject)
        MyBase.New()
        Try
            printDoc = _PrintDoc

            Dim i As Short

            mvarDataFieldList = "Station_ID;Date_Time;PCode;LEW_Pct;Depth_M;CCode;SCode;LCode;QCode;Agency;RCode;Result;Validated;Create_Update;Track_ID"
            mvarDateFieldList = "Date_Time;Date;Time;Time_DST;Julian;Date_Time_Comb;Date_YMD;Year;Month;Day;Hour;Minute"
            For i = 1 To 31
                mvarDayList = mvarDayList & "Day " & i & IIf(i < 31, ";", "")
            Next i
            For i = 1 To 24
                mvarHourList = mvarHourList & "Hour " & i & IIf(i < 24, ";", "")
            Next i
            mvarMonthList = "Jan;Feb;Mar;Apr;May;Jun;Jul;Aug;Sep;Oct;Nov;Dec"
            mvarStoretFieldList = "PCode_Storet;RCode_Storet;Station_ID_Storet;Agency_Storet;Units_Storet"
            mvarConvertFieldList = "PCode_Convert;RCode_Convert;Station_ID_Convert"
            mvarStationFieldList = "Station_ID;Station_Name;Station_Type;Data_Freq;Latitude;Longitude;Elevation;" & _
                                    "Branch_ID;River_Mile;Dr_Area;Agency;Ref_ID;Remarks;Other;Convert_Station_ID;" & _
                                    "HUC;Reach_ID;WaterBodyType;Basin_Name;County_Name;EcoRegion;PlanRegion;" & _
                                    "FIPS_Code;State;Create_Update"
            mvarPCodeFieldList = "PCode;Anal_Name;Units;Test_Method;Det_Limit;Sig_Figs;Min_Limit;Max_Limit;Min_Criteria;" & _
                                 "Max_Criteria;Format_Str;Convert_PCode;Convert_Units;Convert_Mult;" & _
                                 "WQX_CharacteristicName;WQX_ResultSampFraction;WQX_ResultMeasureUnit;WQX_AnalyticalMethodIDContext;" & _
                                 "WQX_AnalyticalMethodID;WQX_AnalyticalMethodName;WQX_AnalyticalMethodQualifType;" & _
                                 "WQX_AnalyticalMethodDesc;Create_Update"
            mvarCCodeFieldList = "CCode;CCode_Name;Description;Convert_CCode"
            mvarRCodeFieldList = "RCode;Description;Convert_RCode;Calc_Stats;Nondetect_PCode;Nondetect_Result;Detect_Mult"
            mvarQCodeFieldList = "QCode;QCode_Name;Description"
            mvarStationGroupFieldList = "StationGroup;Station_ID"
            mvarPCodeGroupFieldList = "PCodeGroup;PCode"
            mvarConvGroupFieldList = "ConvGroup;ConvFrom;ConvTo"
            mvarBranchFieldList = "Branch_ID;Branch_Name;DS_Branch_ID;DS_Branch_RM"
            mvarAgencyFieldList = "Agency;Agency_Name;Description"
            mvarFIPSCodeFieldList = "FIPS_Code;County_Name;State"
            mvarWQXMethodFieldList = "WQX_AnalyticalMethodIDContext;WQX_AnalyticalMethodID;WQX_AnalyticalMethodName;WQX_AnalyticalMethodQualifType;WQX_AnalyticalMethodDesc"
            mvarValidationFieldList = "Field_Name;Valid_Value;Description"
            mvarIgnoreFieldList = "Ignore"

            For i = enumGroups.gDataTable To enumGroups.gMaxGroup
                ValidHeading(i) = Choose(i + 1, mvarDataFieldList, mvarDateFieldList, mvarDayList, mvarHourList, mvarMonthList, mvarStoretFieldList, mvarConvertFieldList, mvarStationFieldList, mvarPCodeFieldList, mvarCCodeFieldList, mvarRCodeFieldList, mvarQCodeFieldList, mvarBranchFieldList, mvarStationGroupFieldList, mvarPCodeGroupFieldList, mvarConvGroupFieldList, mvarAgencyFieldList, mvarFIPSCodeFieldList, mvarWQXMethodFieldList, mvarValidationFieldList, ValidList(enumTables.tStationTable), ValidList(enumTables.tPCodeTable), mvarIgnoreFieldList)
                'note, station, pcode, ccode, rcode lists will be filled in SupportDir event
            Next i

            TableName = New String() {"", "", "", "", "", "", "Stations", "PCodes", "CCodes", "RCodes", "QCodes", "Branches", "StaGrp", "PCodeGrp", "ConvGrp", "Agencies", "FIPSCodes", "WQXMethods", "Validation"}

            'store field widths, so can trim on export (for numeric, set to 99)
            'note that array indices will be 1 minus field number

            arFieldWidth(enumTables.tDataTable) = New Integer() {25, 99, 10, 99, 99, 3, 3, 3, 1, 3, 1, 99, 1, 99}
            arFieldWidth(enumTables.tStationTable) = New Integer() {25, 60, 60, 8, 99, 99, 99, 16, 99, 99, 10, 20, 50, 50, 25, 14, 25, 30, 30, 30, 30, 30, 99, 2, 99}
            arFieldWidth(enumTables.tPCodeTable) = New Integer() {10, 60, 12, 12, 99, 99, 99, 99, 99, 99, 10, 10, 12, 99, 120, 25, 12, 20, 20, 120, 25, 500, 99}
            arFieldWidth(enumTables.tCCodeTable) = New Integer() {3, 30, 30, 3}
            arFieldWidth(enumTables.tRCodeTable) = New Integer() {1, 250, 1, 99, 99, 99, 99}
            arFieldWidth(enumTables.tQCodeTable) = New Integer() {1, 30, 30}
            arFieldWidth(enumTables.tBranchTable) = New Integer() {16, 60, 16, 99}
            arFieldWidth(enumTables.tStationGroupTable) = New Integer() {100, 25}
            arFieldWidth(enumTables.tPCodeGroupTable) = New Integer() {15, 10}
            arFieldWidth(enumTables.tConvGroupTable) = New Integer() {15, 250, 250}
            arFieldWidth(enumTables.tAgencyTable) = New Integer() {3, 30, 30}
            arFieldWidth(enumTables.tFIPSCodes) = New Integer() {99, 50, 2}
            arFieldWidth(enumTables.tWQXMethods) = New Integer() {20, 20, 120, 25, 500}
            arFieldWidth(enumTables.tValidationTable) = New Integer() {40, 120}

            mvarDelimiters = vbTab
            mvarOtherDelimiter = GetSetting(RegApp, "General", "OtherDelimiter", "|")
            mvarValidateSupport = GetSetting(RegApp, "General", "ValidateSupport", CStr(False))
            mvarRCodeConvGroup = GetSetting(RegApp, "General", "ConvertRCodes", CStr(False))
            mvarCultureName = GetSetting(RegApp, "General", "CultureName", "(Current")

            mvarAutoContinueDelay = 10
            mvarAutoContinue = GetSetting(RegApp, "General", "AUTOCONTINUE", CStr(False))

            mvarGridFont = New Font("Tahoma", 8.25)

            WRDBProject = _WRDBProject

            ConvertGroup = New clsConvertGroup

        Catch ex As Exception
            ErrorMsg(, ex)
        End Try
    End Sub

    ''' <summary>
    ''' Convert number as a string to numeric value based on desired culture format
    ''' </summary>
    ''' <param name="Number">String value of number</param>
    Private Function ParseNumber(ByRef Number As String) As Double
        Dim d As Double
        If CultureName.StartsWith("(") Then
            If Double.TryParse(Number, d) Then Return d Else Return Double.MinValue
        Else
            If Double.TryParse(Number, Globalization.NumberStyles.Any, Globalization.CultureInfo.GetCultureInfo(CultureName).NumberFormat, d) Then Return d Else Return Double.MinValue
        End If
        'Try
        '    If CultureName.StartsWith("(") Then
        '        Return Double.Parse(Number)
        '    Else
        '        Return Double.Parse(Number, Globalization.CultureInfo.GetCultureInfo(CultureName).NumberFormat)
        '    End If
        'Catch ex As Exception
        '    Return Double.MinValue
        'End Try
    End Function

    ''' <summary>
    ''' Convert date as a string to date value based on desired culture format
    ''' </summary>
    ''' <param name="DateTime">String value of date-time</param>
    Private Function ParseDate(ByRef DateTime As String) As Date
        Dim d As Date
        If CultureName.StartsWith("(") Then
            If Date.TryParse(DateTime, d) Then Return d Else Return Date.MinValue
        Else
            If Date.TryParse(DateTime, Globalization.CultureInfo.GetCultureInfo(CultureName).DateTimeFormat, Globalization.DateTimeStyles.AssumeLocal, d) Then Return d Else Return Date.MinValue
        End If
        'Try
        '    If CultureName.StartsWith("(") Then
        '        Return Date.Parse(DateTime)
        '    Else
        '        Return Date.Parse(DateTime, Globalization.CultureInfo.GetCultureInfo(CultureName).DateTimeFormat)
        '    End If
        'Catch ex As Exception
        '    Return Date.MinValue
        'End Try
    End Function

    Private Function RemoveBrackets(ByVal s As String) As String
        Return s.Replace("[", "").Replace("]", "").Replace("'", "").Replace("$", "")
    End Function

    Public Overloads Sub Dispose() Implements IDisposable.Dispose
        Dispose(True)
        GC.SuppressFinalize(Me)
    End Sub

    Private disposed As Boolean = False

    Protected Overloads Sub Dispose(ByVal disposing As Boolean)
        If Not Me.disposed Then
            If disposing Then
                'put disposal items here
                If mvarDataSource IsNot Nothing Then
                    mvarDataSource.Dispose()
                    mvarDataSource = Nothing
                End If
            End If
        End If
        Me.disposed = True
    End Sub

    Protected Overrides Sub Finalize()
        Dispose(False)
        MyBase.Finalize()
    End Sub

End Class

''' <summary>
''' Query ConvGrp table and form dictionary of dictionaries; note that all matches are case-insensitive
''' </summary>
Public Class clsConvertGroup

    ''' <summary>
    ''' This is a dictionary of named conversion dictionaries, each item of which contains a 'convert from' and 'convert to' string
    ''' </summary>
    Private dctConversionGroup As New Generic.Dictionary(Of String, Generic.Dictionary(Of String, String))

    Friend Sub New()
        WRDBProject.ResetPDox("ConvGrp")
        For Each ConvGroup As String In WRDBProject.DB.GetRecordList("ConvGrp", "ConvGroup")
            Dim dct As New Generic.Dictionary(Of String, String)
            Dim dr As DbDataReader = WRDBProject.DB.ExecuteReader("SELECT ConvFrom,ConvTo FROM ConvGrp WHERE ConvGroup='{0}'", ConvGroup)
            While dr IsNot Nothing AndAlso dr.Read
                dct.Add(dr.GetString(0).ToUpper, dr.GetString(1).ToUpper)
            End While
            dr.Close()
            dctConversionGroup.Add(ConvGroup.ToUpper, dct)
        Next
    End Sub

    ''' <summary>
    ''' Using ConvGrp table, try to convert value
    ''' </summary>
    ''' <param name="ConvGroup">Name of conversion group</param>
    ''' <param name="Value">Old value to convert (returns new value if found)</param>
    ''' <remarks></remarks>
    Public Sub ConvertValue(ByVal ConvGroup As String, ByRef Value As String)
        Dim dct As Generic.Dictionary(Of String, String) = Nothing
        If ConvGroup <> "(None)" AndAlso dctConversionGroup.TryGetValue(ConvGroup.ToUpper, dct) Then
            Dim newValue As String = ""
            If dct.TryGetValue(Value.ToUpper, newValue) Then Value = newValue
        End If
    End Sub


End Class